File Coverage

blib/lib/Statistics/R/IO/QapEncoding.pm
Criterion Covered Total %
statement 307 337 91.1
branch 101 144 70.1
condition 18 32 56.2
subroutine 61 64 95.3
pod 24 24 100.0
total 511 601 85.0


line stmt bran cond sub pod time code
1             package Statistics::R::IO::QapEncoding;
2             # ABSTRACT: Functions for parsing Rserve packets
3             $Statistics::R::IO::QapEncoding::VERSION = '1.0001';
4 10     10   572 use 5.010;
  10         21  
5              
6 10     10   32 use strict;
  10         11  
  10         189  
7 10     10   34 use warnings FATAL => 'all';
  10         10  
  10         338  
8              
9 10     10   28 use Exporter 'import';
  10         14  
  10         571  
10              
11             our @EXPORT = qw( );
12             our @EXPORT_OK = qw( decode );
13              
14             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], );
15              
16 10     10   50 use Statistics::R::IO::Parser qw( :all );
  10         16  
  10         2227  
17 10     10   45 use Statistics::R::IO::ParserState;
  10         11  
  10         152  
18 10     10   390 use Statistics::R::REXP::Character;
  10         11  
  10         172  
19 10     10   3414 use Statistics::R::REXP::Complex;
  10         17  
  10         207  
20 10     10   402 use Statistics::R::REXP::Double;
  10         11  
  10         156  
21 10     10   370 use Statistics::R::REXP::Integer;
  10         14  
  10         143  
22 10     10   371 use Statistics::R::REXP::List;
  10         11  
  10         139  
23 10     10   415 use Statistics::R::REXP::Logical;
  10         9  
  10         170  
24 10     10   639 use Statistics::R::REXP::Raw;
  10         12  
  10         162  
25 10     10   365 use Statistics::R::REXP::Language;
  10         9  
  10         155  
26 10     10   346 use Statistics::R::REXP::Symbol;
  10         10  
  10         159  
27 10     10   362 use Statistics::R::REXP::Null;
  10         7  
  10         155  
28 10     10   374 use Statistics::R::REXP::GlobalEnvironment;
  10         9  
  10         178  
29 10     10   3773 use Statistics::R::REXP::Unknown;
  10         15  
  10         201  
30 10     10   3329 use Statistics::R::REXP::S4;
  10         14  
  10         193  
31              
32 10     10   41 use Carp;
  10         10  
  10         587  
33              
34             use constant {
35 10         1319 DT_INT => 1, # int
36             DT_CHAR => 2, # char
37             DT_DOUBLE => 3, # double
38             DT_STRING => 4, # zero- terminated string
39             DT_BYTESTREAM => 5, # stream of bytes (unlike DT_STRING may
40             # contain 0)
41             DT_SEXP => 10, # encoded SEXP
42             DT_ARRAY => 11, # array of objects (i.e. first 4 bytes specify how
43             # many subsequent objects are part of the array; 0
44             # is legitimate)
45             DT_CUSTOM => 32, # custom types not defined in the protocol but
46             # used by applications
47             DT_LARGE => 64, # new in 0102: if this flag is set then the length
48             # of the object is coded as 56-bit integer
49             # enlarging the header by 4 bytes
50 10     10   33 };
  10         11  
51              
52             # eXpression Types: transport format of the encoded SEXPs:
53             # [0] int type/len (1 byte type, 3 bytes len - same as SET_PAR)
54             # [4] REXP attr (if bit 8 in type is set)
55             # [4/8] data .. */
56             # Expression type classification:
57             # P = primary type
58             # s = secondary type - its decoding is identical to
59             # a primary type and thus the client doesn't need to
60             # decode it separately.
61             # - = deprecated/removed. if a client doesn't need to
62             # support old Rserve versions, those can be safely skipped.
63             # XT_* types:
64             use constant {
65 10         26056 XT_NULL => 0, # P data: [0]
66             XT_INT => 1, # - data: [4]int
67             XT_DOUBLE => 2, # - data: [8]double
68             XT_STR => 3, # P data: [n]char null-term. strg.
69             XT_LANG => 4, # - data: same as XT_LIST
70             XT_SYM => 5, # - data: [n]char symbol name
71             XT_BOOL => 6, # - data: [1]byte boolean (1=TRUE, 0=FALSE, 2=NA)
72             XT_S4 => 7, # P data: [0]
73             XT_VECTOR => 16, # P data: [?]REXP,REXP,...
74             XT_LIST => 17, # - X head, X vals, X tag (since 0.1-5)
75             XT_CLOS => 18, # P X formals, X body (closure; since 0.1-5)
76             XT_SYMNAME => 19, # s same as XT_STR (since 0.5)
77             XT_LIST_NOTAG => 20, # s same as XT_VECTOR (since 0.5)
78             XT_LIST_TAG => 21, # P X tag, X val, Y tag, Y val, ... (since 0.5)
79             XT_LANG_NOTAG => 22, # s same as XT_LIST_NOTAG (since 0.5)
80             XT_LANG_TAG => 23, # s same as XT_LIST_TAG (since 0.5)
81             XT_VECTOR_EXP => 26, # s same as XT_VECTOR (since 0.5)
82             XT_VECTOR_STR => 27, # - same as XT_VECTOR (since 0.5 but unused, use XT_ARRAY_STR instead)
83             XT_ARRAY_INT => 32, # P data: [n*4]int,int,...
84             XT_ARRAY_DOUBLE => 33, # P data: [n*8]double,double,...
85             XT_ARRAY_STR => 34, # P data: string,string,...
86             # (string=byte,byte,...,0) padded with '\01'
87             XT_ARRAY_BOOL_UA => 35, # - data: [n]byte,byte,... (unaligned! NOT supported anymore)
88             XT_ARRAY_BOOL => 36, # P data: int(n),byte,byte,...
89             XT_RAW => 37, # P data: int(n),byte,byte,...
90             XT_ARRAY_CPLX => 38, # P data: [n*16]double,double,... (Re,Im,Re,Im,...)
91             XT_UNKNOWN => 48, # P data: [4]int - SEXP type (as from TYPEOF(x))
92              
93             XT_LARGE => 64, # new in 0102: if this flag is set then the length
94             # of the object is coded as 56-bit integer
95             # enlarging the header by 4 bytes
96             XT_HAS_ATTR => 128, # flag; if set, the following REXP is the
97             # attribute
98 10     10   34 };
  10         10  
99              
100             sub unpack_sexp_info {
101             bind(\&any_uint32,
102             sub {
103 1098   50 1098   2141 my $object_info = shift // return;
104 1098         1338 my $is_long = $object_info & XT_LARGE;
105              
106 1098 50       1556 if ($is_long) {
107             ## TODO: if `is_long`, then the next 4 bytes contain
108             ## the upper half of the length
109 0         0 error "Sorry, long packets aren't supported yet"
110             } else {
111 1098         4624 mreturn { has_attributes => $object_info & XT_HAS_ATTR,
112             is_long => $is_long,
113             object_type => $object_info & 0x3F,
114             length => $object_info >> 8,
115             }
116             }
117             })
118 1098     1098 1 4313 }
119              
120              
121             sub sexp_data {
122 1098     1098 1 792 my $object_info = shift;
123              
124             bind(maybe_attributes($object_info),
125             sub {
126 1098     1098   902 my ($object_info, $attributes) = @{shift()};
  1098         1008  
127            
128 1098 100       6333 if ($object_info->{object_type} == XT_NULL) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
129             # encoded Nil
130 17         119 mreturn(Statistics::R::REXP::Null->new)
131             } elsif ($object_info->{object_type} == XT_ARRAY_INT) {
132             # integer vector
133 120         241 intsxp($object_info, $attributes)
134             } elsif ($object_info->{object_type} == XT_ARRAY_BOOL) {
135             # logical vector
136 14         34 lglsxp($object_info, $attributes)
137             } elsif ($object_info->{object_type} == XT_ARRAY_DOUBLE) {
138             # numeric vector
139 123         261 dblsxp($object_info, $attributes)
140             } elsif ($object_info->{object_type} == XT_ARRAY_CPLX) {
141             # complex vector
142 21         58 cplxsxp($object_info, $attributes)
143             } elsif ($object_info->{object_type} == XT_ARRAY_STR) {
144             # character vector
145 194         396 strsxp($object_info, $attributes)
146             } elsif ($object_info->{object_type} == XT_RAW) {
147             # raw vector
148 9         24 rawsxp($object_info)
149             } elsif ($object_info->{object_type} == XT_VECTOR) {
150             # list (generic vector)
151 69         160 vecsxp($object_info, $attributes)
152             } elsif ($object_info->{object_type} == XT_VECTOR_EXP) {
153             # expression vector
154 15         39 expsxp($object_info, $attributes)
155             } elsif ($object_info->{object_type} == XT_LIST_NOTAG) {
156             # pairlist with no tags
157 0         0 $object_info->{has_tags} = 0;
158 0         0 listsxp($object_info)
159             } elsif ($object_info->{object_type} == XT_LIST_TAG) {
160             # pairlist with tags
161 100         128 $object_info->{has_tags} = 1;
162 100         201 listsxp($object_info)
163             } elsif ($object_info->{object_type} == XT_LANG_NOTAG) {
164             # language without tags
165 37         65 $object_info->{has_tags} = 0;
166 37         72 langsxp($object_info, $attributes)
167             } elsif ($object_info->{object_type} == XT_LANG_TAG) {
168             # language with tags
169 5         8 $object_info->{has_tags} = 1;
170 5         28 langsxp($object_info, $attributes)
171             } elsif ($object_info->{object_type} == XT_SYMNAME) {
172             # symbol
173 350         549 symsxp($object_info)
174             } elsif ($object_info->{object_type} == XT_CLOS) {
175             # closure
176 0         0 closxp($object_info, $attributes)
177             } elsif ($object_info->{object_type} == XT_UNKNOWN) {
178             # unknown
179 18         44 nosxp($object_info, $attributes)
180             } elsif ($object_info->{object_type} == XT_S4) {
181             # unknown
182 6         18 s4sxp($object_info, $attributes)
183             } else {
184             error "unimplemented XT_TYPE: " . $object_info->{object_type}
185 0         0 }
186             })
187 1098         1272 }
188              
189              
190             sub maybe_attributes {
191 1098     1098 1 956 my $object_info = shift;
192              
193             sub {
194 1098 50   1098   1651 my $state = shift or return;
195 1098         909 my $attributes;
196              
197 1098 100       1908 if ($object_info->{has_attributes}) {
198 100         1835 my $attributes_start = $state->position;
199 100 50       395 my $result = dt_sexp_data()->($state) or return;
200              
201 100         4152 $attributes = { tagged_pairlist_to_attribute_hash(shift @$result) };
202 100         258 $state = shift @$result;
203              
204             ## adjust SEXP length for that already consumed by attributes
205 100         2856 $object_info->{length} -= $state->position - $attributes_start;
206             }
207            
208 1098         2675 [ [$object_info, $attributes], $state]
209             }
210 1098         4742 }
211              
212              
213             sub tagged_pairlist_to_rexp_hash {
214 100 50   100 1 311 my $list = shift or return;
215            
216             croak "Tagged element has an attribute?!"
217             if exists $list->{attributes} &&
218 100 50 33     398 grep {$_ ne 'names'} keys %{$list->{attributes}};
  100         403  
  100         258  
219            
220 100         116 my @elements = @{$list->elements};
  100         1572  
221 100         487 my @names = @{$list->attributes->{names}->elements};
  100         1425  
222 100 50       1846 die 'length of tags does not match the elements' unless
223             scalar(@elements) == scalar(@names);
224              
225 100         125 my %rexps;
226 100         277 while (my $name = shift(@names)) {
227 235         209 my $value = shift(@elements);
228 235         667 $rexps{$name} = $value;
229             }
230             %rexps
231 100         370 }
232              
233              
234             sub tagged_pairlist_to_attribute_hash {
235 100     100 1 240 my %rexp_hash = tagged_pairlist_to_rexp_hash @_;
236            
237 100         170 my $row_names = $rexp_hash{'row.names'};
238 100 100 100     277 if ($row_names && $row_names->type eq 'integer' &&
      66        
239             ! defined $row_names->elements->[0]) {
240             ## compact encoding when rownames are integers 1..n: the
241             ## length n is in the second element, but can be negative to
242             ## denote "automatic" rownames
243 12         470 my $n = abs($row_names->elements->[1]);
244 12         92 $rexp_hash{'row.names'} = Statistics::R::REXP::Integer->new([1..$n]);
245             }
246              
247             %rexp_hash
248 100         381 }
249              
250              
251             sub s4sxp {
252 6     6 1 9 my ($object_info, $attributes) = (shift, shift);
253 6         97 my $class = $attributes->{class}->elements;
254             croak "S4 'class' must be a single-element array" unless
255 6 50 33     37 ref($class) eq 'ARRAY' && scalar(@{$class}) == 1;
  6         24  
256 6         90 my $package = $attributes->{class}->attributes->{package}->elements;
257             croak "S4 'package' must be a single-element array" unless
258 6 50 33     111 ref($package) eq 'ARRAY' && scalar(@{$package}) == 1;
  6         22  
259            
260             # the remaining attributes should be object's slots
261 6         15 delete $attributes->{class};
262 6         63 my $slots = $attributes;
263            
264 6         58 mreturn(Statistics::R::REXP::S4->new(class => $class->[0],
265             package => $package->[0],
266             slots => $slots))
267             }
268              
269             sub symsxp {
270 350     350 1 295 my $object_info = shift;
271            
272             bind(count($object_info->{length}, \&any_char),
273             sub {
274 350 50   350   324 my @chars = @{shift or return};
  350         1220  
275 350   100     3569 pop @chars while @chars && !ord($chars[-1]);
276 350         1672 mreturn(Statistics::R::REXP::Symbol->new(join('', @chars)))
277             })
278 350         807 }
279              
280              
281             sub nosxp {
282 18     18 1 24 my ($object_info, $attributes) = (shift, shift);
283              
284             bind(\&any_uint32,
285             sub {
286 18 50   18   53 my $sexp_id = shift or return;
287              
288 18         50 my %args = (sexptype => $sexp_id);
289 18 100       40 if ($attributes) {
290 3         8 $args{attributes} = $attributes
291             }
292            
293 18         127 mreturn(Statistics::R::REXP::Unknown->new(%args))
294             })
295 18         87 }
296              
297              
298             sub intsxp {
299 120     120 1 152 my ($object_info, $attributes) = (shift, shift);
300            
301 120 50       269 if ($object_info->{length} % 4 == 0) {
302             bind(count($object_info->{length}/4,
303             any_int32_na),
304             sub {
305 120 50   120   126 my @ints = @{shift or return};
  120         403  
306 120         318 my %args = (elements => [@ints]);
307 120 100       224 if ($attributes) {
308 18         37 $args{attributes} = $attributes
309             }
310 120         632 mreturn(Statistics::R::REXP::Integer->new(%args));
311             })
312 120         436 } else {
313             error "TODO: intsxp length doesn't align by 4: " .
314             $object_info->{length}
315 0         0 }
316             }
317              
318              
319             sub dblsxp {
320 123     123 1 190 my ($object_info, $attributes) = (shift, shift);
321            
322 123 50       260 if ($object_info->{length} % 8 == 0) {
323             bind(count($object_info->{length}/8,
324             any_real64_na),
325             sub {
326 123 50   123   127 my @dbls = @{shift or return};
  123         384  
327 123         382 my %args = (elements => [@dbls]);
328 123 100       257 if ($attributes) {
329 18         40 $args{attributes} = $attributes
330             }
331 123         607 mreturn(Statistics::R::REXP::Double->new(%args));
332             })
333 123         441 } else {
334             error "TODO: dblsxp length doesn't align by 8: " .
335             $object_info->{length}
336 0         0 }
337             }
338              
339              
340             sub cplxsxp {
341 21     21 1 31 my ($object_info, $attributes) = (shift, shift);
342            
343 21 50       66 if ($object_info->{length} % 16 == 0) {
344             bind(count($object_info->{length}/8,
345             any_real64_na),
346             sub {
347 21 50   21   30 my @dbls = @{shift or return};
  21         73  
348 21         24 my @cplx;
349 21         83 while (my ($re, $im) = splice(@dbls, 0, 2)) {
350 33 100 66     705 if (defined($re) && defined($im)) {
351 30         89 push(@cplx, Math::Complex::cplx($re, $im))
352             }
353             else {
354 3         9 push(@cplx, undef)
355             }
356             }
357 21         1149 my %args = (elements => [@cplx]);
358 21 100       43 if ($attributes) {
359 3         5 $args{attributes} = $attributes
360             }
361 21         177 mreturn(Statistics::R::REXP::Complex->new(%args));
362             })
363 21         84 } else {
364             error "TODO: cplxsxp length doesn't align by 16: " .
365             $object_info->{length}
366 0         0 }
367             }
368              
369              
370             sub lglsxp {
371 14     14 1 20 my ($object_info, $attributes) = (shift, shift);
372            
373 14         21 my $dt_length = $object_info->{length},;
374 14 50       34 if ($dt_length) {
375             bind(\&any_uint32,
376             sub {
377 14   50 14   41 my $true_length = shift // return;
378 14         33 my $padding_length = $dt_length - $true_length - 4;
379              
380             bind(seq(count($true_length,
381             \&any_uint8),
382             count($padding_length,
383             \&any_uint8)),
384             sub {
385 14 50       19 my ($elements, $padding) = @{shift or return};
  14         35  
386             my %args = (elements => [
387 14 100       15 map { $_ == 2 ? undef : $_ } @{$elements}
  31         79  
  14         22  
388             ]);
389 14 100       31 if ($attributes) {
390 1         2 $args{attributes} = $attributes
391             }
392 14         112 mreturn(Statistics::R::REXP::Logical->new(%args));
393             })
394 14         40 })
395 14         65 } else {
396 0         0 mreturn(Statistics::R::REXP::Logical->new);
397             }
398             }
399              
400              
401             sub rawsxp {
402 9     9 1 12 my $object_info = shift;
403              
404 9         12 my $dt_length = $object_info->{length},;
405 9 50       20 if ($dt_length) {
406             bind(\&any_uint32,
407             sub {
408 9   50 9   27 my $true_length = shift // return;
409 9         21 my $padding_length = $dt_length - $true_length - 4;
410              
411             bind(seq(count($true_length,
412             \&any_uint8),
413             count($padding_length,
414             \&any_uint8)),
415             sub {
416 9 50       11 my ($elements, $padding) = @{shift or return};
  9         27  
417 9         42 mreturn(Statistics::R::REXP::Raw->new($elements));
418             })
419 9         27 })
420 9         44 } else {
421 0         0 mreturn(Statistics::R::REXP::Raw->new);
422             }
423             }
424              
425              
426             sub strsxp {
427 194     194 1 227 my ($object_info, $attributes) = (shift, shift);
428              
429 194         300 my $length = $object_info->{length};
430 194 100       429 if ($length) {
431             sub {
432 188     188   170 my $state = shift;
433 188         3475 my $end_at = $state->position + $length;
434              
435 188         717 my @elements; # elements of the vector
436             my @characters; # characters in the current element
437 188         2913 while ($state->position < $end_at) {
438 3284         87056 my $ch = $state->at;
439 3284 100       57906 if (ord($ch)) {
440 2802         3553 push @characters, $ch;
441             } else {
442 482         991 my $element = join('', @characters);
443 482 100       719 if ($element eq "\xFF") {
444             ## NaStringRepresentation
445 3         4 push @elements, undef;
446             } else {
447             ## unescape real \xFF characters
448 479         540 $element =~ s/\xFF\xFF/\xFF/g;
449 479         620 push @elements, $element;
450             }
451 482         794 @characters = ();
452             }
453 3284         5206 $state = $state->next;
454             }
455            
456 188         5698 my %args = (elements => [@elements]);
457 188 100       361 if ($attributes) {
458 15         25 $args{attributes} = $attributes
459             }
460 188         784 [ Statistics::R::REXP::Character->new(%args), $state ];
461             }
462 188         883 } else {
463 6         32 mreturn(Statistics::R::REXP::Character->new);
464             }
465             }
466              
467              
468             sub vecsxp {
469 84     84 1 118 my ($object_info, $attributes) = (shift, shift);
470              
471 84         121 my $length = $object_info->{length};
472             sub {
473 84     84   99 my $state = shift;
474 84         1498 my $end_at = $state->position + $length;
475            
476 84         332 my @elements;
477 84         1173 while ($state->position < $end_at) {
478 231 50       3422 my $result = dt_sexp_data()->($state) or return;
479            
480 231         8049 push @elements, shift @$result;
481 231         1704 $state = shift @$result;
482             }
483 84         1831 my %args = (elements => [@elements]);
484 84 100       211 if ($attributes) {
485 30         68 $args{attributes} = $attributes
486             }
487 84         412 [ Statistics::R::REXP::List->new(%args), $state ];
488             }
489 84         395 }
490              
491              
492             sub expsxp {
493             bind(vecsxp(@_), sub {
494 15     15   21 my $list = shift;
495 15         229 my %args = (elements => $list->elements);
496 15         252 my $attributes = $list->attributes;
497 15 50       55 if ($attributes) {
498 0         0 $args{attributes} = $attributes
499             }
500 15         119 mreturn(Statistics::R::REXP::Expression->new(%args))
501             })
502 15     15 1 40 }
503              
504              
505             sub tagged_pairlist {
506 142     142 1 152 my $object_info = shift;
507              
508 142         190 my $length = $object_info->{length};
509 142 50       265 if ($length) {
510             sub {
511 142     142   145 my $state = shift;
512 142         2691 my $end_at = $state->position + $length;
513            
514 142         554 my @elements;
515 142         1991 while ($state->position < $end_at) {
516 356 50       4813 my $result = dt_sexp_data()->($state) or return;
517            
518 356         10446 my $value = shift @$result;
519 356         371 $state = shift @$result;
520              
521 356         673 my $element = { value => $value };
522 356 100       2055 if ($object_info->{has_tags}) {
523 250 50       405 $result = dt_sexp_data()->($state) or return;
524 250         6209 my $tag = shift @$result;
525              
526 250 100       656 $element->{tag} = $tag unless $tag->is_null;
527 250         354 $state = shift @$result;
528             }
529            
530 356         2352 push @elements, $element;
531             }
532 142         3169 [ [ @elements ], $state ];
533             }
534 142         1013 } else {
535 0         0 mreturn []
536             }
537             }
538              
539              
540             ## At the REXP level, pairlists are treated the same as generic
541             ## vectors, i.e., as instances of type List. Tags, if present, become
542             ## the name attribute.
543             sub listsxp {
544 100     100 1 106 my $object_info = shift;
545             ## The `tagged_pairlist` returns an array of cons cells, and we
546             ## must separate the tags from the elements before invoking the
547             ## List constructor, with the tags becoming the names attribute
548             bind(tagged_pairlist($object_info),
549             sub {
550 100 50   100   194 my $list = shift or return;
551              
552 100         103 my @elements;
553             my @names;
554 100         200 foreach my $element (@$list) {
555 235         981 my $tag = $element->{tag};
556 235         231 my $value = $element->{value};
557 235         239 push @elements, $value;
558 235 50       824 push @names, $tag ? $tag->name : '';
559             }
560              
561 100         847 my %args = (elements => [ @elements ]);
562             ## if no element is tagged, then don't construct the
563             ## 'names' attribute
564 100 50       204 if (grep {exists $_->{tag}} @$list) {
  235         415  
565             $args{attributes} = {
566 100         359 names => Statistics::R::REXP::Character->new([ @names ])
567             };
568             }
569              
570 100         1006 mreturn(Statistics::R::REXP::List->new(%args))
571             })
572 100         206 }
573              
574              
575             ## Language expressions are pairlists, but with a certain structure:
576             ## - the first element is the reference (name or another language
577             ## expression) to the function call
578             ## - the rest of the list are the arguments of the call, with optional
579             ## tags to name them
580             sub langsxp {
581 42     42 1 62 my ($object_info, $attributes) = (shift, shift);
582             ## After the pairlist has been parsed by `tagged_pairlist`, we
583             ## separate the tags from the elements before invoking the Language
584             ## constructor, with the tags becoming the names attribute
585             bind(tagged_pairlist($object_info),
586             sub {
587 42 50   42   107 my $list = shift or return;
588              
589 42         47 my @elements;
590             my @names;
591 42         75 foreach my $element (@$list) {
592 121         149 my $tag = $element->{tag};
593 121         95 my $value = $element->{value};
594 121         120 push @elements, $value;
595 121 100       224 push @names, $tag ? $tag->name : '';
596             }
597              
598 42         139 my %args = (elements => [ @elements ]);
599             ## if no element is tagged, then don't construct the
600             ## 'names' attribute
601 42 100       67 if (grep {exists $_->{tag}} @$list) {
  121         309  
602 5   50     25 $attributes //= {}; # initialize the hash
603 5         25 $attributes->{names} = Statistics::R::REXP::Character->new([ @names ]);
604             }
605 42 100       118 $args{attributes} = $attributes if $attributes;
606              
607 42         207 mreturn(Statistics::R::REXP::Language->new(%args))
608             })
609 42         91 }
610              
611              
612             sub closxp {
613 0     0 1 0 my ($object_info, $attributes) = (shift, shift);
614            
615 0         0 my $length = $object_info->{length};
616             bind(count(2, dt_sexp_data()),
617             sub {
618 0 0   0   0 my ($args, $body) = @{(shift or return)};
  0         0  
619 0         0 my (@arg_names, @arg_values);
620 0 0       0 if (ref $args eq ref []) {
621 0         0 foreach my $arg (@{$args}) {
  0         0  
622 0         0 push @arg_names, $arg->{tag}->name;
623 0 0       0 if (Statistics::R::REXP::Symbol->new('') eq $arg->{value}) {
624 0         0 push @arg_values, undef
625             }
626             else {
627             push @arg_values, $arg->{value}
628 0         0 }
629             }
630             }
631            
632 0         0 my %args = (body => $body,
633             args => [@arg_names],
634             defaults => [@arg_values]);
635            
636 0 0       0 $args{attributes} = $attributes if $attributes;
637            
638 0         0 mreturn(Statistics::R::REXP::Closure->new(%args))
639             })
640 0         0 }
641              
642             sub dt_sexp_data {
643 1098     1098 1 1625 bind(unpack_sexp_info,
644             \&sexp_data)
645             }
646              
647              
648             sub decode_sexp {
649             bind(seq(uint8(DT_SEXP), \&any_uint24,
650             dt_sexp_data),
651             sub {
652 161     161   378 mreturn shift->[2]
653             })
654 161     161 1 1856 }
655              
656              
657             sub decode_int {
658 0     0 1 0 die 'TODO: implement'
659             }
660              
661              
662             sub decode {
663 161     161 1 69472 my $data = shift;
664 161 50 33     489 return error "Decode requires a scalar data or array reference" if ref $data && ref $data ne ref [];
665              
666 161         412 endianness('<');
667            
668 161         855 my $result =
669             decode_sexp->(Statistics::R::IO::ParserState->new(data => $data));
670            
671 161 50       1570 if ($result) {
672 161         1152 my $state = $result->[1];
673 161 50       385 carp("remaining data: " . (scalar(@{$state->data}) - $state->position))
  0         0  
674             unless $state->eof;
675             }
676            
677 161         1102 $result;
678             }
679              
680              
681             1;
682              
683             __END__