File Coverage

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


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.0002';
4 12     12   500 use 5.010;
  12         40  
5              
6 12     12   68 use strict;
  12         23  
  12         305  
7 12     12   65 use warnings FATAL => 'all';
  12         29  
  12         426  
8              
9 12     12   55 use Exporter 'import';
  12         22  
  12         774  
10              
11             our @EXPORT = qw( );
12             our @EXPORT_OK = qw( unserialize );
13              
14             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], );
15              
16 12     12   2271 use Statistics::R::IO::Parser qw( :all );
  12         38  
  12         2682  
17 12     12   3142 use Statistics::R::IO::ParserState;
  12         36  
  12         381  
18 12     12   3626 use Statistics::R::REXP::Character;
  12         33  
  12         390  
19 12     12   3363 use Statistics::R::REXP::Double;
  12         31  
  12         320  
20 12     12   3130 use Statistics::R::REXP::Integer;
  12         32  
  12         309  
21 12     12   3325 use Statistics::R::REXP::List;
  12         32  
  12         314  
22 12     12   3225 use Statistics::R::REXP::Logical;
  12         40  
  12         372  
23 12     12   3273 use Statistics::R::REXP::Raw;
  12         35  
  12         349  
24 12     12   3019 use Statistics::R::REXP::Language;
  12         27  
  12         349  
25 12     12   3255 use Statistics::R::REXP::Symbol;
  12         30  
  12         285  
26 12     12   3402 use Statistics::R::REXP::Null;
  12         29  
  12         301  
27 12     12   3325 use Statistics::R::REXP::Closure;
  12         36  
  12         350  
28 12     12   3430 use Statistics::R::REXP::GlobalEnvironment;
  12         37  
  12         422  
29 12     12   3551 use Statistics::R::REXP::EmptyEnvironment;
  12         32  
  12         326  
30 12     12   3112 use Statistics::R::REXP::BaseEnvironment;
  12         32  
  12         321  
31              
32 12     12   69 use Carp;
  12         24  
  12         30973  
33              
34             sub header {
35 788     788 1 10240 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   1739 endianness('>');
48 622         1470 mreturn shift;
49             })
50 788     788 1 2532 }
51              
52              
53             sub bin {
54             bind(string("B\n"), # "binary" header
55             sub {
56 166     166   475 endianness('<');
57 166         392 mreturn shift;
58             })
59 788     788 1 1853 }
60              
61              
62             sub object_content {
63 13106     13106 1 22111 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   43543 my $object_info = shift or return;
72 21045         109273 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 52243 }
81              
82              
83             sub object_data {
84 21037     21037 1 29415 my $object_info = shift;
85            
86 21037 100       146833 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         227 lglsxp($object_info)
89             } elsif ($object_info->{object_type} == 13) {
90             # integer vector
91 1064         3524 intsxp($object_info)
92             } elsif ($object_info->{object_type} == 14) {
93             # numeric vector
94 1082         3453 realsxp($object_info)
95             } elsif ($object_info->{object_type} == 15) {
96             # complex vector
97 91         222 cplxsxp($object_info)
98             } elsif ($object_info->{object_type} == 16) {
99             # character vector
100 1777         5140 strsxp($object_info)
101             } elsif ($object_info->{object_type} == 24) {
102             # raw vector
103 48         158 rawsxp($object_info)
104             } elsif ($object_info->{object_type} == 19) {
105             # list (generic vector)
106 631         2312 vecsxp($object_info)
107             } elsif ($object_info->{object_type} == 20) {
108             # expression vector
109 65         149 expsxp($object_info)
110             } elsif ($object_info->{object_type} == 9) {
111             # internal character string
112 6439         15294 charsxp($object_info)
113             } elsif ($object_info->{object_type} == 2) {
114             # pairlist
115 3182         8752 listsxp($object_info)
116             } elsif ($object_info->{object_type} == 6) {
117             # language object
118 363         1180 langsxp($object_info)
119             } elsif ($object_info->{object_type} == 1) {
120             # symbol
121 1880         5333 symsxp($object_info)
122             } elsif ($object_info->{object_type} == 4) {
123             # environment
124 52         203 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         77 s4sxp($object_info)
131             } elsif ($object_info->{object_type} == 0xfb) {
132             # encoded R_MissingArg, i.e., empty symbol
133 13         70 mreturn(Statistics::R::REXP::Symbol->new)
134             } elsif ($object_info->{object_type} == 0xf1) {
135             # encoded R_BaseEnv
136 13         93 mreturn(Statistics::R::REXP::BaseEnvironment->new)
137             } elsif ($object_info->{object_type} == 0xf2) {
138             # encoded R_EmptyEnv
139 13         98 mreturn(Statistics::R::REXP::EmptyEnvironment->new)
140             } elsif ($object_info->{object_type} == 0xfd) {
141             # encoded R_GlobalEnv
142 110         806 mreturn(Statistics::R::REXP::GlobalEnvironment->new)
143             } elsif ($object_info->{object_type} == 0xfe) {
144             # encoded Nil
145 2482         10014 mreturn(Statistics::R::REXP::Null->new)
146             } elsif ($object_info->{object_type} == 0xff) {
147             # encoded reference to a stored singleton
148 1631         4807 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 5675 my $object_info = shift;
157 3545         5964 my $sub_items = 1; # CAR, CDR will be read separately
158 3545 100       8817 if ($object_info->{has_attributes}) {
159 63         127 $sub_items++;
160             }
161 3545 100       7789 if ($object_info->{has_tag}) {
162 2591         4580 $sub_items++;
163             }
164            
165             bind(seq(bind(count($sub_items, object_content),
166             sub {
167 3545 50   3545   6114 my @args = @{shift or return};
  3545         10927  
168 3545         10738 my %value = (value => $args[-1]);
169 3545 100       12211 $value{tag} = $args[-2] if $object_info->{has_tag};
170 3545 100       8950 $value{attributes} = $args[0] if $object_info->{has_attributes};
171 3545         14838 mreturn { %value };
172             }),
173             object_content), # CDR
174             sub {
175 3545 50   3545   5442 my ($car, $cdr) = @{shift or return};
  3545         8709  
176 3545         7073 my @elements = ($car);
177 3545 100       12644 if (ref $cdr eq ref []) {
    50          
178 2155         3344 push( @elements, @{$cdr})
  2155         4146  
179             }
180             elsif (!$cdr->is_null) {
181 0         0 push( @elements, $cdr)
182             }
183 3545         8953 mreturn [ @elements ]
184             })
185 3545         9935 }
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   1176 my $list = shift or return;
200 363         1150 my @elements;
201             my @names;
202 363         0 my %attributes;
203 363         931 foreach my $element (@$list) {
204 1044         1799 my $tag = $element->{tag};
205 1044         1546 my $value = $element->{value};
206 1044         1640 push @elements, $value;
207 1044 100       2389 push @names, $tag ? $tag->name : '';
208              
209 1044 100       3139 if (exists $element->{attributes}) {
210 63         236 my %attribute_hash = tagged_pairlist_to_attribute_hash($element->{attributes});
211 63         402 while(my ($key, $value) = each %attribute_hash) {
212             die "Duplicate attribute $key" if
213 630 50       1144 exists $attributes{$key};
214 630         1526 $attributes{$key} = $value;
215             }
216             }
217             }
218 363         1271 my %args = (elements => [ @elements ]);
219             ## if no element is tagged, then don't construct the
220             ## 'names' attribute
221 363 100       866 if (grep {exists $_->{tag}} @$list) {
  1044         2497  
222 45         298 $attributes{names} = Statistics::R::REXP::Character->new([ @names ]);
223             }
224 363 100       1338 $args{attributes} = \%attributes if %attributes;
225              
226 363         2211 mreturn(Statistics::R::REXP::Language->new(%args))
227             })
228 363     363 1 1110 }
229              
230              
231             sub tagged_pairlist_to_rexp_hash {
232 2005     2005 1 3194 my $list = shift;
233 2005 100       6108 return unless ref $list eq ref [];
234              
235 1024         2092 my %rexps;
236 1024         2895 foreach my $element (@$list) {
237             croak "Tagged element has an attribute?!"
238 2495 50       5566 if exists $element->{attribute};
239 2495         42736 my $name = $element->{tag}->name;
240 2495         17690 $rexps{$name} = $element->{value};
241             }
242             %rexps
243 1024         4605 }
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 3228 my %rexp_hash = tagged_pairlist_to_rexp_hash @_;
251            
252 902         2452 my $row_names = $rexp_hash{'row.names'};
253 902 100 100     3662 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         3668 my $n = abs($row_names->elements->[1]);
259 89         829 $rexp_hash{'row.names'} = Statistics::R::REXP::Integer->new([1..$n]);
260             }
261              
262             %rexp_hash
263 902         4562 }
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   8580 my $len = shift;
274 4833 100       11305 if ($len >= 0) {
    100          
275 4823         10908 mreturn $len;
276             } elsif ($len == -1) {
277 5         14 error 'TODO: Long vectors are not supported';
278             } else {
279 5         23 error 'Negative length detected: ' . $len;
280             }
281             })
282 4833     4833 1 18940 }
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 12171 my ($object_info, $element_parser, $rexp_class) = @_;
289              
290 4694         12180 my @parsers = ( with_count(maybe_long_length, $element_parser) );
291 4694 100       14154 if ($object_info->{has_attributes}) {
292 787         2264 push @parsers, object_content
293             }
294              
295             bind(seq(@parsers),
296             sub {
297 4685 50   4685   8131 my @args = @{shift or return};
  4685         13745  
298 4685   50     18468 my %args = (elements => (shift(@args) || []));
299 4685 100       14786 if ($object_info->{has_attributes}) {
300 787         2594 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
301             }
302 4685         28135 mreturn($rexp_class->new(%args))
303             })
304 4694         12525 }
305              
306              
307             sub lglsxp {
308 75     75 1 126 my $object_info = shift;
309             vector_and_attributes($object_info,
310             bind(\&any_uint32,
311             sub {
312 197     197   303 my $x = shift;
313 197 100       506 mreturn ($x != 0x80000000 ?
314             $x : undef)
315 75         287 }),
316             'Statistics::R::REXP::Logical')
317             }
318              
319              
320             sub intsxp {
321 1064     1064 1 2119 my $object_info = shift;
322 1064         3593 vector_and_attributes($object_info,
323             any_int32_na,
324             'Statistics::R::REXP::Integer')
325             }
326              
327              
328             sub realsxp {
329 1082     1082 1 2512 my $object_info = shift;
330 1082         4040 vector_and_attributes($object_info,
331             any_real64_na,
332             'Statistics::R::REXP::Double')
333             }
334              
335              
336             sub cplxsxp {
337 91     91 1 139 my $object_info = shift;
338            
339 91         217 my @parsers = ( with_count(maybe_long_length, count(2, any_real64_na)) );
340 91 100       282 if ($object_info->{has_attributes}) {
341 13         27 push @parsers, object_content
342             }
343              
344             bind(seq(@parsers),
345             sub {
346 91 50   91   136 my @args = @{shift or return};
  91         265  
347 91 50       148 my @elements = @{shift(@args) || []};
  91         257  
348 91         156 my @cplx;
349 91         176 foreach my $element (@elements) {
350 143         2898 my ($re, $im) = @{$element};
  143         242  
351 143 100 66     537 if (defined($re) && defined($im)) {
352 130         382 push(@cplx, Math::Complex::cplx($re, $im))
353             }
354             else {
355 13         28 push(@cplx, undef)
356             }
357             }
358 91         4286 my %args = (elements => [ @cplx ]);
359 91 100       281 if ($object_info->{has_attributes}) {
360 13         36 $args{attributes} = { tagged_pairlist_to_attribute_hash(shift @args) };
361             }
362 91         593 mreturn(Statistics::R::REXP::Complex->new(%args))
363             })
364 91         208 }
365              
366              
367             sub strsxp {
368 1777     1777 1 3362 my $object_info = shift;
369 1777         4395 vector_and_attributes($object_info, object_content,
370             'Statistics::R::REXP::Character')
371             }
372              
373              
374             sub rawsxp {
375 48     48 1 85 my $object_info = shift;
376             return error "No attributes are allowed on raw vectors"
377 48 50       143 if $object_info->{has_attributes};
378              
379             bind(with_count(maybe_long_length, \&any_uint8),
380             sub {
381 46   50 46   450 mreturn(Statistics::R::REXP::Raw->new(shift or return));
382             })
383 48         160 }
384              
385              
386             sub vecsxp {
387 631     631 1 1492 my $object_info = shift;
388 631         1964 vector_and_attributes($object_info, object_content,
389             'Statistics::R::REXP::List')
390             }
391              
392              
393             sub expsxp {
394 65     65 1 95 my $object_info = shift;
395 65         129 vector_and_attributes($object_info, object_content,
396             'Statistics::R::REXP::Expression')
397             }
398              
399              
400             sub charsxp {
401 6439     6439 1 10182 my $object_info = shift;
402             ## TODO: handle character set encodings (UTF8, LATIN1, native)
403             bind(\&any_int32,
404             sub {
405 6439     6439   10637 my $len = shift;
406 6439 100       12685 if ($len >= 0) {
    100          
407             bind(count( $len, \&any_char),
408             sub {
409 6424 50       10419 my @chars = @{shift or return};
  6424         22803  
410 6424         26890 mreturn join('', @chars);
411             })
412 6424         16719 } elsif ($len == -1) {
413 14         35 mreturn undef;
414             } else {
415 1         4 error 'Negative length detected: ' . $len;
416             }
417             })
418 6439         28536 }
419              
420              
421             sub symsxp {
422 1880     1880 1 3368 my $object_info = shift;
423             bind(object_content, # should be followed by a charsxp
424             sub {
425 1880   50 1880   10306 add_singleton(Statistics::R::REXP::Symbol->new(shift or return));
426             })
427 1880         4251 }
428              
429              
430             sub refsxp {
431 1631     1631 1 2801 my $object_info = shift;
432 1631         3816 my $ref_id = $object_info->{flags} >> 8;
433 1631 50       4329 return error 'TODO: only packed reference ids' if $ref_id == 0;
434 1631         5832 get_singleton($ref_id-1)
435             }
436              
437              
438             sub envsxp {
439 52     52 1 126 my $object_info = shift;
440             reserve_singleton(
441             bind(\&any_uint32,
442             sub {
443 52     52   107 my $locked = shift;
444             bind(count(4, object_content),
445             sub {
446 52         117 my ($enclosure, $frame, $hash, $attributes) = @{$_[0]};
  52         164  
447            
448             ## Frame is a tagged pairlist with tag the symbol and CAR the value
449 52         195 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       284 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         126 foreach my $chain (@{$hash->elements}) {
  52         958  
456             ## Hash chain is a tagged pairlist
457 988         1691 my %chain_vars = tagged_pairlist_to_rexp_hash $chain;
458            
459             ## Merge the variables from the hash chain
460 988         2256 while (my ($name, $value) = each %chain_vars) {
461 59 50 50     351 $vars{$name} = $value unless exists $vars{$name} and
462             die "Variable $name is already defined in the environment";
463             }
464             }
465             }
466            
467 52         247 my %args = (
468             frame => \%vars,
469             enclosure => $enclosure,
470             );
471 52 100       258 if (ref $attributes eq ref []) {
472 13         41 $args{attributes} = { tagged_pairlist_to_attribute_hash $attributes };
473             }
474 52         463 mreturn(Statistics::R::REXP::Environment->new( %args ));
475             })
476 52         282 }))
  52         168  
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 45 my $object_info = shift;
521             bind(object_content,
522             sub {
523 26     26   50 my $attr = shift;
524 26         74 my $attributes = { tagged_pairlist_to_attribute_hash($attr) };
525 26         439 my $class = $attributes->{class}->elements;
526             croak "S4 'class' must be a single-element array" unless
527 26 50 33     210 ref($class) eq 'ARRAY' && scalar(@{$class}) == 1;
  26         107  
528 26         427 my $package = $attributes->{class}->attributes->{package}->elements;
529             croak "S4 'package' must be a single-element array" unless
530 26 50 33     588 ref($package) eq 'ARRAY' && scalar(@{$package}) == 1;
  26         106  
531            
532             # the remaining attributes should be object's slots
533 26         63 delete $attributes->{class};
534 26         43 my $slots = $attributes;
535            
536 26         203 mreturn(Statistics::R::REXP::S4->new(class => $class->[0],
537             package => $package->[0],
538             slots => $slots))
539             })
540 26         66 }
541              
542              
543             sub unserialize {
544 780     780 1 23375 my $data = shift;
545 780 50 66     2334 return error "Unserialize requires a scalar data" if ref $data && ref $data ne ref [];
546              
547 780         4484 my $result =
548             bind(header,
549             \&object_content,
550             )->(Statistics::R::IO::ParserState->new(data => $data));
551            
552 769 50       38441 if ($result) {
553 769         8257 my $state = $result->[1];
554 769 50       2218 carp("remaining data: " . (scalar(@{$state->data}) - $state->position))
  0         0  
555             unless $state->eof;
556             }
557            
558 769         6867 $result;
559             }
560              
561              
562             1;
563              
564             __END__