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.0';
4 10     10   570 use 5.010;
  10         21  
5              
6 10     10   36 use strict;
  10         10  
  10         191  
7 10     10   35 use warnings FATAL => 'all';
  10         10  
  10         315  
8              
9 10     10   33 use Exporter 'import';
  10         10  
  10         663  
10              
11             our @EXPORT = qw( );
12             our @EXPORT_OK = qw( unserialize );
13              
14             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], );
15              
16 10     10   2197 use Statistics::R::IO::Parser qw( :all );
  10         25  
  10         2217  
17 10     10   4154 use Statistics::R::IO::ParserState;
  10         16  
  10         256  
18 10     10   3751 use Statistics::R::REXP::Character;
  10         16  
  10         232  
19 10     10   3643 use Statistics::R::REXP::Double;
  10         15  
  10         237  
20 10     10   3566 use Statistics::R::REXP::Integer;
  10         16  
  10         223  
21 10     10   3404 use Statistics::R::REXP::List;
  10         15  
  10         235  
22 10     10   3459 use Statistics::R::REXP::Logical;
  10         17  
  10         220  
23 10     10   3447 use Statistics::R::REXP::Raw;
  10         19  
  10         246  
24 10     10   3356 use Statistics::R::REXP::Language;
  10         15  
  10         242  
25 10     10   3486 use Statistics::R::REXP::Symbol;
  10         16  
  10         192  
26 10     10   3327 use Statistics::R::REXP::Null;
  10         15  
  10         210  
27 10     10   3535 use Statistics::R::REXP::Closure;
  10         16  
  10         209  
28 10     10   3649 use Statistics::R::REXP::GlobalEnvironment;
  10         14  
  10         265  
29 10     10   3445 use Statistics::R::REXP::EmptyEnvironment;
  10         17  
  10         233  
30 10     10   3498 use Statistics::R::REXP::BaseEnvironment;
  10         14  
  10         212  
31              
32 10     10   40 use Carp;
  10         11  
  10         22354  
33              
34             sub header {
35 322     322 1 3420 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 262     262   491 endianness('>');
48 262         511 mreturn shift;
49             })
50 322     322 1 877 }
51              
52              
53             sub bin {
54             bind(string("B\n"), # "binary" header
55             sub {
56 60     60   133 endianness('<');
57 60         113 mreturn shift;
58             })
59 322     322 1 597 }
60              
61              
62             sub object_content {
63 4858     4858 1 5785 bind(&unpack_object_info,
64             \&object_data)
65             }
66              
67              
68             sub unpack_object_info {
69             bind(\&any_uint32,
70             sub {
71 7753 50   7753   12621 my $object_info = shift or return;
72 7753         33430 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 4866     4866 1 16777 }
81              
82              
83             sub object_data {
84 7745     7745 1 6450 my $object_info = shift;
85            
86 7745 100       49536 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 27         55 lglsxp($object_info)
89             } elsif ($object_info->{object_type} == 13) {
90             # integer vector
91 396         905 intsxp($object_info)
92             } elsif ($object_info->{object_type} == 14) {
93             # numeric vector
94 394         1005 realsxp($object_info)
95             } elsif ($object_info->{object_type} == 15) {
96             # complex vector
97 35         64 cplxsxp($object_info)
98             } elsif ($object_info->{object_type} == 16) {
99             # character vector
100 657         1619 strsxp($object_info)
101             } elsif ($object_info->{object_type} == 24) {
102             # raw vector
103 20         50 rawsxp($object_info)
104             } elsif ($object_info->{object_type} == 19) {
105             # list (generic vector)
106 235         944 vecsxp($object_info)
107             } elsif ($object_info->{object_type} == 20) {
108             # expression vector
109 25         55 expsxp($object_info)
110             } elsif ($object_info->{object_type} == 9) {
111             # internal character string
112 2365         3776 charsxp($object_info)
113             } elsif ($object_info->{object_type} == 2) {
114             # pairlist
115 1162         1988 listsxp($object_info)
116             } elsif ($object_info->{object_type} == 6) {
117             # language object
118 135         314 langsxp($object_info)
119             } elsif ($object_info->{object_type} == 1) {
120             # symbol
121 706         1397 symsxp($object_info)
122             } elsif ($object_info->{object_type} == 4) {
123             # environment
124 20         72 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 10         21 s4sxp($object_info)
131             } elsif ($object_info->{object_type} == 0xfb) {
132             # encoded R_MissingArg, i.e., empty symbol
133 5         27 mreturn(Statistics::R::REXP::Symbol->new)
134             } elsif ($object_info->{object_type} == 0xf1) {
135             # encoded R_BaseEnv
136 5         42 mreturn(Statistics::R::REXP::BaseEnvironment->new)
137             } elsif ($object_info->{object_type} == 0xf2) {
138             # encoded R_EmptyEnv
139 5         46 mreturn(Statistics::R::REXP::EmptyEnvironment->new)
140             } elsif ($object_info->{object_type} == 0xfd) {
141             # encoded R_GlobalEnv
142 42         299 mreturn(Statistics::R::REXP::GlobalEnvironment->new)
143             } elsif ($object_info->{object_type} == 0xfe) {
144             # encoded Nil
145 924         3013 mreturn(Statistics::R::REXP::Null->new)
146             } elsif ($object_info->{object_type} == 0xff) {
147             # encoded reference to a stored singleton
148 577         1295 refsxp($object_info)
149             } else {
150             error "unimplemented SEXPTYPE: " . $object_info->{object_type}
151 0         0 }
152             }
153              
154              
155             sub listsxp {
156 1297     1297 1 1235 my $object_info = shift;
157 1297         1245 my $sub_items = 1; # CAR, CDR will be read separately
158 1297 100       2090 if ($object_info->{has_attributes}) {
159 23         31 $sub_items++;
160             }
161 1297 100       2298 if ($object_info->{has_tag}) {
162 943         1028 $sub_items++;
163             }
164            
165             bind(seq(bind(count($sub_items, object_content),
166             sub {
167 1297 50   1297   1220 my @args = @{shift or return};
  1297         3162  
168 1297         3248 my %value = (value => $args[-1]);
169 1297 100       3158 $value{tag} = $args[-2] if $object_info->{has_tag};
170 1297 100       2321 $value{attributes} = $args[0] if $object_info->{has_attributes};
171 1297         4313 mreturn { %value };
172             }),
173             object_content), # CDR
174             sub {
175 1297 50   1297   1128 my ($car, $cdr) = @{shift or return};
  1297         2328  
176 1297         1653 my @elements = ($car);
177 1297 100       3814 if (ref $cdr eq ref []) {
    50          
178 785         702 push( @elements, @{$cdr})
  785         1028  
179             }
180             elsif (!$cdr->is_null) {
181 0         0 push( @elements, $cdr)
182             }
183 1297         2721 mreturn [ @elements ]
184             })
185 1297         2501 }
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 135 50   135   380 my $list = shift or return;
200 135         185 my @elements;
201             my @names;
202 0         0 my %attributes;
203 135         240 foreach my $element (@$list) {
204 388         396 my $tag = $element->{tag};
205 388         376 my $value = $element->{value};
206 388         411 push @elements, $value;
207 388 100       641 push @names, $tag ? $tag->name : '';
208              
209 388 100       914 if (exists $element->{attributes}) {
210 23         71 my %attribute_hash = tagged_pairlist_to_attribute_hash($element->{attributes});
211 23         112 while(my ($key, $value) = each %attribute_hash) {
212             die "Duplicate attribute $key" if
213 230 50       287 exists $attributes{$key};
214 230         407 $attributes{$key} = $value;
215             }
216             }
217             }
218 135         370 my %args = (elements => [ @elements ]);
219             ## if no element is tagged, then don't construct the
220             ## 'names' attribute
221 135 100       225 if (grep {exists $_->{tag}} @$list) {
  388         646  
222 17         99 $attributes{names} = Statistics::R::REXP::Character->new([ @names ]);
223             }
224 135 100       384 $args{attributes} = \%attributes if %attributes;
225              
226 135         703 mreturn(Statistics::R::REXP::Language->new(%args))
227             })
228 135     135 1 312 }
229              
230              
231             sub tagged_pairlist_to_rexp_hash {
232 743     743 1 648 my $list = shift;
233 743 100       1694 return unless ref $list eq ref [];
234              
235 374         565 my %rexps;
236 374         627 foreach my $element (@$list) {
237             croak "Tagged element has an attribute?!"
238 903 50       1441 if exists $element->{attribute};
239 903         14409 my $name = $element->{tag}->name;
240 903         4342 $rexps{$name} = $element->{value};
241             }
242             %rexps
243 374         1356 }
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 330     330 1 939 my %rexp_hash = tagged_pairlist_to_rexp_hash @_;
251            
252 330         641 my $row_names = $rexp_hash{'row.names'};
253 330 100 100     1133 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 33         1226 my $n = abs($row_names->elements->[1]);
259 33         262 $rexp_hash{'row.names'} = Statistics::R::REXP::Integer->new([1..$n]);
260             }
261              
262             %rexp_hash
263 330         1376 }
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 1789     1789   1691 my $len = shift;
274 1789 100       3162 if ($len >= 0) {
    100          
275 1779         3561 mreturn $len;
276             } elsif ($len == -1) {
277 5         13 error 'TODO: Long vectors are not supported';
278             } else {
279 5         17 error 'Negative length detected: ' . $len;
280             }
281             })
282 1789     1789 1 5703 }
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 1734     1734 1 2357 my ($object_info, $element_parser, $rexp_class) = @_;
289              
290 1734         3397 my @parsers = ( with_count(maybe_long_length, $element_parser) );
291 1734 100       4198 if ($object_info->{has_attributes}) {
292 287         610 push @parsers, object_content
293             }
294              
295             bind(seq(@parsers),
296             sub {
297 1725 50   1725   1741 my @args = @{shift or return};
  1725         4301  
298 1725   50     5420 my %args = (elements => (shift(@args) || []));
299 1725 100       4259 if ($object_info->{has_attributes}) {
300 287         830 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
301             }
302 1725         9294 mreturn($rexp_class->new(%args))
303             })
304 1734         3835 }
305              
306              
307             sub lglsxp {
308 27     27 1 31 my $object_info = shift;
309             vector_and_attributes($object_info,
310             bind(\&any_uint32,
311             sub {
312 69     69   58 my $x = shift;
313 69 100       166 mreturn ($x != 0x80000000 ?
314             $x : undef)
315 27         88 }),
316             'Statistics::R::REXP::Logical')
317             }
318              
319              
320             sub intsxp {
321 396     396 1 411 my $object_info = shift;
322 396         1053 vector_and_attributes($object_info,
323             any_int32_na,
324             'Statistics::R::REXP::Integer')
325             }
326              
327              
328             sub realsxp {
329 394     394 1 494 my $object_info = shift;
330 394         1309 vector_and_attributes($object_info,
331             any_real64_na,
332             'Statistics::R::REXP::Double')
333             }
334              
335              
336             sub cplxsxp {
337 35     35 1 36 my $object_info = shift;
338            
339 35         59 my @parsers = ( with_count(maybe_long_length, count(2, any_real64_na)) );
340 35 100       95 if ($object_info->{has_attributes}) {
341 5         10 push @parsers, object_content
342             }
343              
344             bind(seq(@parsers),
345             sub {
346 35 50   35   39 my @args = @{shift or return};
  35         96  
347 35 50       32 my @elements = @{shift(@args) || []};
  35         86  
348 35         34 my @cplx;
349 35         46 foreach my $element (@elements) {
350 55         830 my ($re, $im) = @{$element};
  55         65  
351 55 100 66     186 if (defined($re) && defined($im)) {
352 50         119 push(@cplx, Math::Complex::cplx($re, $im))
353             }
354             else {
355 5         11 push(@cplx, undef)
356             }
357             }
358 35         1361 my %args = (elements => [ @cplx ]);
359 35 100       80 if ($object_info->{has_attributes}) {
360 5         15 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
361             }
362 35         234 mreturn(Statistics::R::REXP::Complex->new(%args))
363             })
364 35         70 }
365              
366              
367             sub strsxp {
368 657     657 1 1195 my $object_info = shift;
369 657         1281 vector_and_attributes($object_info, object_content,
370             'Statistics::R::REXP::Character')
371             }
372              
373              
374             sub rawsxp {
375 20     20 1 39 my $object_info = shift;
376             return error "No attributes are allowed on raw vectors"
377 20 50       57 if $object_info->{has_attributes};
378              
379             bind(with_count(maybe_long_length, \&any_uint8),
380             sub {
381 18   50 18   164 mreturn(Statistics::R::REXP::Raw->new(shift or return));
382             })
383 20         48 }
384              
385              
386             sub vecsxp {
387 235     235 1 322 my $object_info = shift;
388 235         602 vector_and_attributes($object_info, object_content,
389             'Statistics::R::REXP::List')
390             }
391              
392              
393             sub expsxp {
394 25     25 1 29 my $object_info = shift;
395 25         49 vector_and_attributes($object_info, object_content,
396             'Statistics::R::REXP::Expression')
397             }
398              
399              
400             sub charsxp {
401 2365     2365 1 2586 my $object_info = shift;
402             ## TODO: handle character set encodings (UTF8, LATIN1, native)
403             bind(\&any_int32,
404             sub {
405 2365     2365   2060 my $len = shift;
406 2365 100       3735 if ($len >= 0) {
    100          
407             bind(count( $len, \&any_char),
408             sub {
409 2358 50       2210 my @chars = @{shift or return};
  2358         7036  
410 2358         7374 mreturn join('', @chars);
411             })
412 2358         5002 } elsif ($len == -1) {
413 6         15 mreturn undef;
414             } else {
415 1         6 error 'Negative length detected: ' . $len;
416             }
417             })
418 2365         8813 }
419              
420              
421             sub symsxp {
422 706     706 1 736 my $object_info = shift;
423             bind(object_content, # should be followed by a charsxp
424             sub {
425 706   50 706   3127 add_singleton(Statistics::R::REXP::Symbol->new(shift or return));
426             })
427 706         1090 }
428              
429              
430             sub refsxp {
431 577     577 1 563 my $object_info = shift;
432 577         830 my $ref_id = $object_info->{flags} >> 8;
433 577 50       1175 return error 'TODO: only packed reference ids' if $ref_id == 0;
434 577         1593 get_singleton($ref_id-1)
435             }
436              
437              
438             sub envsxp {
439 20     20 1 32 my $object_info = shift;
440             reserve_singleton(
441             bind(\&any_uint32,
442             sub {
443 20     20   32 my $locked = shift;
444             bind(count(4, object_content),
445             sub {
446 20         30 my ($enclosure, $frame, $hash, $attributes) = @{$_[0]};
  20         44  
447            
448             ## Frame is a tagged pairlist with tag the symbol and CAR the value
449 20         64 my %vars = tagged_pairlist_to_rexp_hash $frame;
450              
451             ## Hash table is a Null or a VECSXP with hash chain per element
452 20 50       97 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 20         34 foreach my $chain (@{$hash->elements}) {
  20         365  
456             ## Hash chain is a tagged pairlist
457 372         404 my %chain_vars = tagged_pairlist_to_rexp_hash $chain;
458            
459             ## Merge the variables from the hash chain
460 372         715 while (my ($name, $value) = each %chain_vars) {
461 23 50 50     130 $vars{$name} = $value unless exists $vars{$name} and
462             die "Variable $name is already defined in the environment";
463             }
464             }
465             }
466            
467 20         82 my %args = (
468             frame => \%vars,
469             enclosure => $enclosure,
470             );
471 20 100       68 if (ref $attributes eq ref []) {
472 5         15 $args{attributes} = { tagged_pairlist_to_attribute_hash $attributes };
473             }
474 20         192 mreturn(Statistics::R::REXP::Environment->new( %args ));
475             })
476 20         99 }))
  20         46  
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 10     10 1 12 my $object_info = shift;
521             bind(object_content,
522             sub {
523 10     10   14 my $attr = shift;
524 10         19 my $attributes = { tagged_pairlist_to_attribute_hash($attr) };
525 10         153 my $class = $attributes->{class}->elements;
526             croak "S4 'class' must be a single-element array" unless
527 10 50 33     57 ref($class) eq 'ARRAY' && scalar(@{$class}) == 1;
  10         34  
528 10         202 my $package = $attributes->{class}->attributes->{package}->elements;
529             croak "S4 'package' must be a single-element array" unless
530 10 50 33     202 ref($package) eq 'ARRAY' && scalar(@{$package}) == 1;
  10         32  
531            
532             # the remaining attributes should be object's slots
533 10         14 delete $attributes->{class};
534 10         11 my $slots = $attributes;
535            
536 10         75 mreturn(Statistics::R::REXP::S4->new(class => $class->[0],
537             package => $package->[0],
538             slots => $slots))
539             })
540 10         20 }
541              
542              
543             sub unserialize {
544 314     314 1 16418 my $data = shift;
545 314 50 66     758 return error "Unserialize requires a scalar data" if ref $data && ref $data ne ref [];
546              
547 314         1827 my $result =
548             bind(header,
549             \&object_content,
550             )->(Statistics::R::IO::ParserState->new(data => $data));
551            
552 303 50       14495 if ($result) {
553 303         2262 my $state = $result->[1];
554 303 50       717 carp("remaining data: " . (scalar(@{$state->data}) - $state->position))
  0         0  
555             unless $state->eof;
556             }
557            
558 303         2103 $result;
559             }
560              
561              
562             1;
563              
564             __END__