File Coverage

blib/lib/Statistics/R/IO/REXPFactory.pm
Criterion Covered Total %
statement 254 279 91.0
branch 104 132 78.7
condition 15 29 51.7
subroutine 61 63 96.8
pod 27 27 100.0
total 461 530 86.9


line stmt bran cond sub pod time code
1             package Statistics::R::IO::REXPFactory;
2             # ABSTRACT: Functions for parsing R data files
3             $Statistics::R::IO::REXPFactory::VERSION = '1.0001';
4 12     12   590 use 5.010;
  12         27  
5              
6 12     12   36 use strict;
  12         15  
  12         220  
7 12     12   36 use warnings FATAL => 'all';
  12         12  
  12         348  
8              
9 12     12   33 use Exporter 'import';
  12         13  
  12         754  
10              
11             our @EXPORT = qw( );
12             our @EXPORT_OK = qw( unserialize );
13              
14             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], );
15              
16 12     12   2888 use Statistics::R::IO::Parser qw( :all );
  12         15  
  12         2556  
17 12     12   4574 use Statistics::R::IO::ParserState;
  12         22  
  12         302  
18 12     12   4293 use Statistics::R::REXP::Character;
  12         19  
  12         282  
19 12     12   4124 use Statistics::R::REXP::Double;
  12         20  
  12         280  
20 12     12   4215 use Statistics::R::REXP::Integer;
  12         16  
  12         259  
21 12     12   4099 use Statistics::R::REXP::List;
  12         20  
  12         261  
22 12     12   4012 use Statistics::R::REXP::Logical;
  12         17  
  12         259  
23 12     12   4089 use Statistics::R::REXP::Raw;
  12         17  
  12         279  
24 12     12   4005 use Statistics::R::REXP::Language;
  12         20  
  12         273  
25 12     12   3948 use Statistics::R::REXP::Symbol;
  12         19  
  12         235  
26 12     12   4120 use Statistics::R::REXP::Null;
  12         16  
  12         264  
27 12     12   4250 use Statistics::R::REXP::Closure;
  12         16  
  12         280  
28 12     12   4221 use Statistics::R::REXP::GlobalEnvironment;
  12         21  
  12         308  
29 12     12   4154 use Statistics::R::REXP::EmptyEnvironment;
  12         21  
  12         266  
30 12     12   4099 use Statistics::R::REXP::BaseEnvironment;
  12         19  
  12         301  
31              
32 12     12   48 use Carp;
  12         12  
  12         26656  
33              
34             sub header {
35 788     788 1 8027 seq(choose(xdr(),
36             bin()),
37             uint32(2), # serialization format v2
38             \&any_uint32, # creator's R version
39             uint32(0x020300) # min R version to read (2.3.0 as of 3.0.2)
40             )
41             }
42              
43              
44             sub xdr {
45             bind(string("X\n"), # XDR header
46             sub {
47 622     622   1157 endianness('>');
48 622         1121 mreturn shift;
49             })
50 788     788 1 2089 }
51              
52              
53             sub bin {
54             bind(string("B\n"), # "binary" header
55             sub {
56 166     166   372 endianness('<');
57 166         314 mreturn shift;
58             })
59 788     788 1 1523 }
60              
61              
62             sub object_content {
63 13106     13106 1 13516 bind(&unpack_object_info,
64             \&object_data)
65             }
66              
67              
68             sub unpack_object_info {
69             bind(\&any_uint32,
70             sub {
71 21045 50   21045   31288 my $object_info = shift or return;
72 21045         81015 mreturn { is_object => $object_info & 1<<8,
73             has_attributes => $object_info & 1<<9,
74             has_tag => $object_info & 1<<10,
75             object_type => $object_info & 0xFF,
76             levels => $object_info >> 12,
77             flags => $object_info,
78             };
79             })
80 13114     13114 1 38054 }
81              
82              
83             sub object_data {
84 21037     21037 1 17934 my $object_info = shift;
85            
86 21037 100       120919 if ($object_info->{object_type} == 10) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
87             # logical vector
88 75         142 lglsxp($object_info)
89             } elsif ($object_info->{object_type} == 13) {
90             # integer vector
91 1064         1806 intsxp($object_info)
92             } elsif ($object_info->{object_type} == 14) {
93             # numeric vector
94 1082         1765 realsxp($object_info)
95             } elsif ($object_info->{object_type} == 15) {
96             # complex vector
97 91         181 cplxsxp($object_info)
98             } elsif ($object_info->{object_type} == 16) {
99             # character vector
100 1777         3189 strsxp($object_info)
101             } elsif ($object_info->{object_type} == 24) {
102             # raw vector
103 48         118 rawsxp($object_info)
104             } elsif ($object_info->{object_type} == 19) {
105             # list (generic vector)
106 631         1509 vecsxp($object_info)
107             } elsif ($object_info->{object_type} == 20) {
108             # expression vector
109 65         131 expsxp($object_info)
110             } elsif ($object_info->{object_type} == 9) {
111             # internal character string
112 6439         8771 charsxp($object_info)
113             } elsif ($object_info->{object_type} == 2) {
114             # pairlist
115 3182         4746 listsxp($object_info)
116             } elsif ($object_info->{object_type} == 6) {
117             # language object
118 363         605 langsxp($object_info)
119             } elsif ($object_info->{object_type} == 1) {
120             # symbol
121 1880         3085 symsxp($object_info)
122             } elsif ($object_info->{object_type} == 4) {
123             # environment
124 52         120 envsxp($object_info)
125             } elsif ($object_info->{object_type} == 3) {
126             # closure
127 0         0 closxp($object_info)
128             } elsif ($object_info->{object_type} == 25) {
129             # closure
130 26         69 s4sxp($object_info)
131             } elsif ($object_info->{object_type} == 0xfb) {
132             # encoded R_MissingArg, i.e., empty symbol
133 13         69 mreturn(Statistics::R::REXP::Symbol->new)
134             } elsif ($object_info->{object_type} == 0xf1) {
135             # encoded R_BaseEnv
136 13         94 mreturn(Statistics::R::REXP::BaseEnvironment->new)
137             } elsif ($object_info->{object_type} == 0xf2) {
138             # encoded R_EmptyEnv
139 13         96 mreturn(Statistics::R::REXP::EmptyEnvironment->new)
140             } elsif ($object_info->{object_type} == 0xfd) {
141             # encoded R_GlobalEnv
142 110         644 mreturn(Statistics::R::REXP::GlobalEnvironment->new)
143             } elsif ($object_info->{object_type} == 0xfe) {
144             # encoded Nil
145 2482         6705 mreturn(Statistics::R::REXP::Null->new)
146             } elsif ($object_info->{object_type} == 0xff) {
147             # encoded reference to a stored singleton
148 1631         2446 refsxp($object_info)
149             } else {
150             error "unimplemented SEXPTYPE: " . $object_info->{object_type}
151 0         0 }
152             }
153              
154              
155             sub listsxp {
156 3545     3545 1 2744 my $object_info = shift;
157 3545         2887 my $sub_items = 1; # CAR, CDR will be read separately
158 3545 100       5429 if ($object_info->{has_attributes}) {
159 63         96 $sub_items++;
160             }
161 3545 100       5345 if ($object_info->{has_tag}) {
162 2591         2296 $sub_items++;
163             }
164            
165             bind(seq(bind(count($sub_items, object_content),
166             sub {
167 3545 50   3545   2975 my @args = @{shift or return};
  3545         8350  
168 3545         6827 my %value = (value => $args[-1]);
169 3545 100       7510 $value{tag} = $args[-2] if $object_info->{has_tag};
170 3545 100       5049 $value{attributes} = $args[0] if $object_info->{has_attributes};
171 3545         10368 mreturn { %value };
172             }),
173             object_content), # CDR
174             sub {
175 3545 50   3545   2701 my ($car, $cdr) = @{shift or return};
  3545         5919  
176 3545         4022 my @elements = ($car);
177 3545 100       8941 if (ref $cdr eq ref []) {
    50          
178 2155         1701 push( @elements, @{$cdr})
  2155         2552  
179             }
180             elsif (!$cdr->is_null) {
181 0         0 push( @elements, $cdr)
182             }
183 3545         6873 mreturn [ @elements ]
184             })
185 3545         5234 }
186              
187              
188             ## Language expressions are pairlists, but with a certain structure:
189             ## - the first element is the reference (name or another language
190             ## expression) to the function call
191             ## - the rest of the list are the arguments of the call, with optional
192             ## tags to name them
193             sub langsxp {
194             ## After the pairlist has been parsed by `listsxp`, we want to
195             ## separate the tags from the elements before invoking the Language
196             ## constructor, with the tags becoming the names attribute
197             bind(listsxp(@_),
198             sub {
199 363 50   363   720 my $list = shift or return;
200 363         360 my @elements;
201             my @names;
202 0         0 my %attributes;
203 363         599 foreach my $element (@$list) {
204 1044         950 my $tag = $element->{tag};
205 1044         840 my $value = $element->{value};
206 1044         940 push @elements, $value;
207 1044 100       1492 push @names, $tag ? $tag->name : '';
208              
209 1044 100       2239 if (exists $element->{attributes}) {
210 63         150 my %attribute_hash = tagged_pairlist_to_attribute_hash($element->{attributes});
211 63         308 while(my ($key, $value) = each %attribute_hash) {
212             die "Duplicate attribute $key" if
213 630 50       720 exists $attributes{$key};
214 630         1126 $attributes{$key} = $value;
215             }
216             }
217             }
218 363         894 my %args = (elements => [ @elements ]);
219             ## if no element is tagged, then don't construct the
220             ## 'names' attribute
221 363 100       544 if (grep {exists $_->{tag}} @$list) {
  1044         1617  
222 45         214 $attributes{names} = Statistics::R::REXP::Character->new([ @names ]);
223             }
224 363 100       900 $args{attributes} = \%attributes if %attributes;
225              
226 363         1607 mreturn(Statistics::R::REXP::Language->new(%args))
227             })
228 363     363 1 641 }
229              
230              
231             sub tagged_pairlist_to_rexp_hash {
232 2005     2005 1 1705 my $list = shift;
233 2005 100       4000 return unless ref $list eq ref [];
234              
235 1024         1032 my %rexps;
236 1024         1648 foreach my $element (@$list) {
237             croak "Tagged element has an attribute?!"
238 2495 50       3796 if exists $element->{attribute};
239 2495         37608 my $name = $element->{tag}->name;
240 2495         10744 $rexps{$name} = $element->{value};
241             }
242             %rexps
243 1024         3458 }
244              
245              
246             ## Attributes are recorded as a pairlist, with attribute name in the
247             ## element's tag, and attribute value in the element itself. Pairlists
248             ## that serialize attributes should not have their own attribute.
249             sub tagged_pairlist_to_attribute_hash {
250 902     902 1 2010 my %rexp_hash = tagged_pairlist_to_rexp_hash @_;
251            
252 902         1339 my $row_names = $rexp_hash{'row.names'};
253 902 100 100     2327 if ($row_names && $row_names->type eq 'integer' &&
      66        
254             ! defined $row_names->elements->[0]) {
255             ## compact encoding when rownames are integers 1..n: the
256             ## length n is in the second element, but can be negative to
257             ## denote "automatic" rownames
258 89         3116 my $n = abs($row_names->elements->[1]);
259 89         590 $rexp_hash{'row.names'} = Statistics::R::REXP::Integer->new([1..$n]);
260             }
261              
262             %rexp_hash
263 902         3430 }
264              
265              
266             ## Vector lengths are encoded as signed integers. This was fine when
267             ## the maximum allowed length was 2^31-1; long vectors were introduced
268             ## in R 3.0 and their length is encoded in three bytes: -1, followed
269             ## by high and low word of a 64-bit length.
270             sub maybe_long_length {
271             bind(\&any_int32,
272             sub {
273 4833     4833   3928 my $len = shift;
274 4833 100       6506 if ($len >= 0) {
    100          
275 4823         8063 mreturn $len;
276             } elsif ($len == -1) {
277 5         10 error 'TODO: Long vectors are not supported';
278             } else {
279 5         18 error 'Negative length detected: ' . $len;
280             }
281             })
282 4833     4833 1 12262 }
283              
284              
285             ## Vectors are serialized first with a SEXP for the vector elements,
286             ## followed by attributes stored as a tagged pairlist.
287             sub vector_and_attributes {
288 4694     4694 1 6358 my ($object_info, $element_parser, $rexp_class) = @_;
289              
290 4694         6465 my @parsers = ( with_count(maybe_long_length, $element_parser) );
291 4694 100       9699 if ($object_info->{has_attributes}) {
292 787         1297 push @parsers, object_content
293             }
294              
295             bind(seq(@parsers),
296             sub {
297 4685 50   4685   3814 my @args = @{shift or return};
  4685         10637  
298 4685   50     12771 my %args = (elements => (shift(@args) || []));
299 4685 100       8832 if ($object_info->{has_attributes}) {
300 787         1803 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
301             }
302 4685         20748 mreturn($rexp_class->new(%args))
303             })
304 4694         8313 }
305              
306              
307             sub lglsxp {
308 75     75 1 81 my $object_info = shift;
309             vector_and_attributes($object_info,
310             bind(\&any_uint32,
311             sub {
312 197     197   162 my $x = shift;
313 197 100       587 mreturn ($x != 0x80000000 ?
314             $x : undef)
315 75         256 }),
316             'Statistics::R::REXP::Logical')
317             }
318              
319              
320             sub intsxp {
321 1064     1064 1 1056 my $object_info = shift;
322 1064         2507 vector_and_attributes($object_info,
323             any_int32_na,
324             'Statistics::R::REXP::Integer')
325             }
326              
327              
328             sub realsxp {
329 1082     1082 1 1134 my $object_info = shift;
330 1082         2627 vector_and_attributes($object_info,
331             any_real64_na,
332             'Statistics::R::REXP::Double')
333             }
334              
335              
336             sub cplxsxp {
337 91     91 1 110 my $object_info = shift;
338            
339 91         202 my @parsers = ( with_count(maybe_long_length, count(2, any_real64_na)) );
340 91 100       274 if ($object_info->{has_attributes}) {
341 13         24 push @parsers, object_content
342             }
343              
344             bind(seq(@parsers),
345             sub {
346 91 50   91   110 my @args = @{shift or return};
  91         337  
347 91 50       97 my @elements = @{shift(@args) || []};
  91         249  
348 91         100 my @cplx;
349 91         141 foreach my $element (@elements) {
350 143         2299 my ($re, $im) = @{$element};
  143         186  
351 143 100 66     543 if (defined($re) && defined($im)) {
352 130         355 push(@cplx, Math::Complex::cplx($re, $im))
353             }
354             else {
355 13         21 push(@cplx, undef)
356             }
357             }
358 91         3795 my %args = (elements => [ @cplx ]);
359 91 100       255 if ($object_info->{has_attributes}) {
360 13         37 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
361             }
362 91         980 mreturn(Statistics::R::REXP::Complex->new(%args))
363             })
364 91         174 }
365              
366              
367             sub strsxp {
368 1777     1777 1 1807 my $object_info = shift;
369 1777         2505 vector_and_attributes($object_info, object_content,
370             'Statistics::R::REXP::Character')
371             }
372              
373              
374             sub rawsxp {
375 48     48 1 53 my $object_info = shift;
376             return error "No attributes are allowed on raw vectors"
377 48 50       129 if $object_info->{has_attributes};
378              
379             bind(with_count(maybe_long_length, \&any_uint8),
380             sub {
381 46   50 46   399 mreturn(Statistics::R::REXP::Raw->new(shift or return));
382             })
383 48         109 }
384              
385              
386             sub vecsxp {
387 631     631 1 751 my $object_info = shift;
388 631         1181 vector_and_attributes($object_info, object_content,
389             'Statistics::R::REXP::List')
390             }
391              
392              
393             sub expsxp {
394 65     65 1 64 my $object_info = shift;
395 65         111 vector_and_attributes($object_info, object_content,
396             'Statistics::R::REXP::Expression')
397             }
398              
399              
400             sub charsxp {
401 6439     6439 1 4765 my $object_info = shift;
402             ## TODO: handle character set encodings (UTF8, LATIN1, native)
403             bind(\&any_int32,
404             sub {
405 6439     6439   5548 my $len = shift;
406 6439 100       8452 if ($len >= 0) {
    100          
407             bind(count( $len, \&any_char),
408             sub {
409 6424 50       5008 my @chars = @{shift or return};
  6424         17603  
410 6424         17390 mreturn join('', @chars);
411             })
412 6424         12027 } elsif ($len == -1) {
413 14         31 mreturn undef;
414             } else {
415 1         4 error 'Negative length detected: ' . $len;
416             }
417             })
418 6439         20736 }
419              
420              
421             sub symsxp {
422 1880     1880 1 1520 my $object_info = shift;
423             bind(object_content, # should be followed by a charsxp
424             sub {
425 1880   50 1880   7252 add_singleton(Statistics::R::REXP::Symbol->new(shift or return));
426             })
427 1880         2606 }
428              
429              
430             sub refsxp {
431 1631     1631 1 1392 my $object_info = shift;
432 1631         1771 my $ref_id = $object_info->{flags} >> 8;
433 1631 50       2517 return error 'TODO: only packed reference ids' if $ref_id == 0;
434 1631         3366 get_singleton($ref_id-1)
435             }
436              
437              
438             sub envsxp {
439 52     52 1 73 my $object_info = shift;
440             reserve_singleton(
441             bind(\&any_uint32,
442             sub {
443 52     52   62 my $locked = shift;
444             bind(count(4, object_content),
445             sub {
446 52         66 my ($enclosure, $frame, $hash, $attributes) = @{$_[0]};
  52         97  
447            
448             ## Frame is a tagged pairlist with tag the symbol and CAR the value
449 52         131 my %vars = tagged_pairlist_to_rexp_hash $frame;
450              
451             ## Hash table is a Null or a VECSXP with hash chain per element
452 52 50       210 if ($hash->can('elements')) {
453             ## It appears that a variable appears either in the frame *or*
454             ## in the hash table, so we have to merge the two
455 52         51 foreach my $chain (@{$hash->elements}) {
  52         845  
456             ## Hash chain is a tagged pairlist
457 988         1025 my %chain_vars = tagged_pairlist_to_rexp_hash $chain;
458            
459             ## Merge the variables from the hash chain
460 988         1577 while (my ($name, $value) = each %chain_vars) {
461 59 50 50     260 $vars{$name} = $value unless exists $vars{$name} and
462             die "Variable $name is already defined in the environment";
463             }
464             }
465             }
466            
467 52         160 my %args = (
468             frame => \%vars,
469             enclosure => $enclosure,
470             );
471 52 100       148 if (ref $attributes eq ref []) {
472 13         33 $args{attributes} = { tagged_pairlist_to_attribute_hash $attributes };
473             }
474 52         311 mreturn(Statistics::R::REXP::Environment->new( %args ));
475             })
476 52         199 }))
  52         91  
477             }
478              
479             sub closxp {
480 0     0 1 0 my $object_info = $_[0];
481            
482             bind(listsxp(@_),
483             sub {
484 0     0   0 my ($head, $body) = @{shift()};
  0         0  
485            
486 0         0 my $attributes = $head->{attributes};
487 0         0 my $environment = $head->{tag};
488 0         0 my $arguments = $head->{value};
489            
490 0         0 my (@arg_names, @arg_defaults);
491 0 0       0 if (ref $arguments eq ref []) {
492 0         0 foreach my $arg (@{$arguments}) {
  0         0  
493 0         0 push @arg_names, $arg->{tag}->name;
494            
495 0         0 my $default = $arg->{value};
496 0 0       0 if (Statistics::R::REXP::Symbol->new('') eq $default) {
497 0         0 push @arg_defaults, undef
498             }
499             else {
500 0         0 push @arg_defaults, $default
501             }
502             }
503             }
504            
505 0   0     0 my %args = (
506             body => $body // Statistics::R::REXP::Null->new,
507             args => [@arg_names],
508             defaults => [@arg_defaults],
509             environment => $environment);
510 0 0       0 if ($object_info->{has_attributes}) {
511 0         0 $args{attributes} = { tagged_pairlist_to_attribute_hash $attributes };
512             }
513            
514 0         0 mreturn(Statistics::R::REXP::Closure->new( %args ));
515             })
516 0         0 }
517              
518              
519             sub s4sxp {
520 26     26 1 33 my $object_info = shift;
521             bind(object_content,
522             sub {
523 26     26   39 my $attr = shift;
524 26         59 my $attributes = { tagged_pairlist_to_attribute_hash($attr) };
525 26         429 my $class = $attributes->{class}->elements;
526             croak "S4 'class' must be a single-element array" unless
527 26 50 33     168 ref($class) eq 'ARRAY' && scalar(@{$class}) == 1;
  26         109  
528 26         399 my $package = $attributes->{class}->attributes->{package}->elements;
529             croak "S4 'package' must be a single-element array" unless
530 26 50 33     514 ref($package) eq 'ARRAY' && scalar(@{$package}) == 1;
  26         98  
531            
532             # the remaining attributes should be object's slots
533 26         50 delete $attributes->{class};
534 26         43 my $slots = $attributes;
535            
536 26         222 mreturn(Statistics::R::REXP::S4->new(class => $class->[0],
537             package => $package->[0],
538             slots => $slots))
539             })
540 26         48 }
541              
542              
543             sub unserialize {
544 780     780 1 15382 my $data = shift;
545 780 50 66     1863 return error "Unserialize requires a scalar data" if ref $data && ref $data ne ref [];
546              
547 780         4137 my $result =
548             bind(header,
549             \&object_content,
550             )->(Statistics::R::IO::ParserState->new(data => $data));
551            
552 769 50       38695 if ($result) {
553 769         6006 my $state = $result->[1];
554 769 50       1899 carp("remaining data: " . (scalar(@{$state->data}) - $state->position))
  0         0  
555             unless $state->eof;
556             }
557            
558 769         5330 $result;
559             }
560              
561              
562             1;
563              
564             __END__