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.0002';
4 10     10   520 use 5.010;
  10         34  
5              
6 10     10   47 use strict;
  10         20  
  10         244  
7 10     10   50 use warnings FATAL => 'all';
  10         20  
  10         419  
8              
9 10     10   48 use Exporter 'import';
  10         36  
  10         757  
10              
11             our @EXPORT = qw( );
12             our @EXPORT_OK = qw( decode );
13              
14             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ], );
15              
16 10     10   62 use Statistics::R::IO::Parser qw( :all );
  10         33  
  10         2540  
17 10     10   73 use Statistics::R::IO::ParserState;
  10         31  
  10         224  
18 10     10   294 use Statistics::R::REXP::Character;
  10         23  
  10         199  
19 10     10   2688 use Statistics::R::REXP::Complex;
  10         33  
  10         294  
20 10     10   406 use Statistics::R::REXP::Double;
  10         20  
  10         205  
21 10     10   302 use Statistics::R::REXP::Integer;
  10         21  
  10         185  
22 10     10   290 use Statistics::R::REXP::List;
  10         17  
  10         191  
23 10     10   280 use Statistics::R::REXP::Logical;
  10         105  
  10         378  
24 10     10   397 use Statistics::R::REXP::Raw;
  10         30  
  10         212  
25 10     10   292 use Statistics::R::REXP::Language;
  10         20  
  10         211  
26 10     10   300 use Statistics::R::REXP::Symbol;
  10         18  
  10         276  
27 10     10   325 use Statistics::R::REXP::Null;
  10         17  
  10         236  
28 10     10   285 use Statistics::R::REXP::GlobalEnvironment;
  10         18  
  10         231  
29 10     10   3192 use Statistics::R::REXP::Unknown;
  10         25  
  10         239  
30 10     10   3092 use Statistics::R::REXP::S4;
  10         28  
  10         265  
31              
32 10     10   58 use Carp;
  10         18  
  10         696  
33              
34             use constant {
35 10         1419 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   59 };
  10         24  
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         28940 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   54 };
  10         18  
99              
100             sub unpack_sexp_info {
101             bind(\&any_uint32,
102             sub {
103 1098   50 1098   2873 my $object_info = shift // return;
104 1098         2281 my $is_long = $object_info & XT_LARGE;
105              
106 1098 50       2383 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         5681 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 5782 }
119              
120              
121             sub sexp_data {
122 1098     1098 1 1649 my $object_info = shift;
123              
124             bind(maybe_attributes($object_info),
125             sub {
126 1098     1098   1560 my ($object_info, $attributes) = @{shift()};
  1098         1990  
127            
128 1098 100       7650 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         134 mreturn(Statistics::R::REXP::Null->new)
131             } elsif ($object_info->{object_type} == XT_ARRAY_INT) {
132             # integer vector
133 120         349 intsxp($object_info, $attributes)
134             } elsif ($object_info->{object_type} == XT_ARRAY_BOOL) {
135             # logical vector
136 14         49 lglsxp($object_info, $attributes)
137             } elsif ($object_info->{object_type} == XT_ARRAY_DOUBLE) {
138             # numeric vector
139 123         387 dblsxp($object_info, $attributes)
140             } elsif ($object_info->{object_type} == XT_ARRAY_CPLX) {
141             # complex vector
142 21         77 cplxsxp($object_info, $attributes)
143             } elsif ($object_info->{object_type} == XT_ARRAY_STR) {
144             # character vector
145 194         622 strsxp($object_info, $attributes)
146             } elsif ($object_info->{object_type} == XT_RAW) {
147             # raw vector
148 9         32 rawsxp($object_info)
149             } elsif ($object_info->{object_type} == XT_VECTOR) {
150             # list (generic vector)
151 69         246 vecsxp($object_info, $attributes)
152             } elsif ($object_info->{object_type} == XT_VECTOR_EXP) {
153             # expression vector
154 15         50 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         228 $object_info->{has_tags} = 1;
162 100         310 listsxp($object_info)
163             } elsif ($object_info->{object_type} == XT_LANG_NOTAG) {
164             # language without tags
165 37         77 $object_info->{has_tags} = 0;
166 37         112 langsxp($object_info, $attributes)
167             } elsif ($object_info->{object_type} == XT_LANG_TAG) {
168             # language with tags
169 5         12 $object_info->{has_tags} = 1;
170 5         19 langsxp($object_info, $attributes)
171             } elsif ($object_info->{object_type} == XT_SYMNAME) {
172             # symbol
173 350         915 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         61 nosxp($object_info, $attributes)
180             } elsif ($object_info->{object_type} == XT_S4) {
181             # unknown
182 6         20 s4sxp($object_info, $attributes)
183             } else {
184             error "unimplemented XT_TYPE: " . $object_info->{object_type}
185 0         0 }
186             })
187 1098         2172 }
188              
189              
190             sub maybe_attributes {
191 1098     1098 1 1685 my $object_info = shift;
192              
193             sub {
194 1098 50   1098   2295 my $state = shift or return;
195 1098         1473 my $attributes;
196              
197 1098 100       2627 if ($object_info->{has_attributes}) {
198 100         2080 my $attributes_start = $state->position;
199 100 50       701 my $result = dt_sexp_data()->($state) or return;
200              
201 100         5670 $attributes = { tagged_pairlist_to_attribute_hash(shift @$result) };
202 100         307 $state = shift @$result;
203              
204             ## adjust SEXP length for that already consumed by attributes
205 100         3473 $object_info->{length} -= $state->position - $attributes_start;
206             }
207            
208 1098         3675 [ [$object_info, $attributes], $state]
209             }
210 1098         6045 }
211              
212              
213             sub tagged_pairlist_to_rexp_hash {
214 100 50   100 1 370 my $list = shift or return;
215            
216             croak "Tagged element has an attribute?!"
217             if exists $list->{attributes} &&
218 100 50 33     425 grep {$_ ne 'names'} keys %{$list->{attributes}};
  100         438  
  100         338  
219            
220 100         208 my @elements = @{$list->elements};
  100         1742  
221 100         614 my @names = @{$list->attributes->{names}->elements};
  100         1509  
222 100 50       2126 die 'length of tags does not match the elements' unless
223             scalar(@elements) == scalar(@names);
224              
225 100         191 my %rexps;
226 100         333 while (my $name = shift(@names)) {
227 235         338 my $value = shift(@elements);
228 235         754 $rexps{$name} = $value;
229             }
230             %rexps
231 100         454 }
232              
233              
234             sub tagged_pairlist_to_attribute_hash {
235 100     100 1 302 my %rexp_hash = tagged_pairlist_to_rexp_hash @_;
236            
237 100         240 my $row_names = $rexp_hash{'row.names'};
238 100 100 100     309 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         550 my $n = abs($row_names->elements->[1]);
244 12         139 $rexp_hash{'row.names'} = Statistics::R::REXP::Integer->new([1..$n]);
245             }
246              
247             %rexp_hash
248 100         431 }
249              
250              
251             sub s4sxp {
252 6     6 1 29 my ($object_info, $attributes) = (shift, shift);
253 6         103 my $class = $attributes->{class}->elements;
254             croak "S4 'class' must be a single-element array" unless
255 6 50 33     48 ref($class) eq 'ARRAY' && scalar(@{$class}) == 1;
  6         28  
256 6         93 my $package = $attributes->{class}->attributes->{package}->elements;
257             croak "S4 'package' must be a single-element array" unless
258 6 50 33     126 ref($package) eq 'ARRAY' && scalar(@{$package}) == 1;
  6         24  
259            
260             # the remaining attributes should be object's slots
261 6         21 delete $attributes->{class};
262 6         97 my $slots = $attributes;
263            
264 6         67 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 551 my $object_info = shift;
271            
272             bind(count($object_info->{length}, \&any_char),
273             sub {
274 350 50   350   573 my @chars = @{shift or return};
  350         1624  
275 350   100     3560 pop @chars while @chars && !ord($chars[-1]);
276 350         2312 mreturn(Statistics::R::REXP::Symbol->new(join('', @chars)))
277             })
278 350         1029 }
279              
280              
281             sub nosxp {
282 18     18 1 53 my ($object_info, $attributes) = (shift, shift);
283              
284             bind(\&any_uint32,
285             sub {
286 18 50   18   89 my $sexp_id = shift or return;
287              
288 18         67 my %args = (sexptype => $sexp_id);
289 18 100       57 if ($attributes) {
290 3         10 $args{attributes} = $attributes
291             }
292            
293 18         158 mreturn(Statistics::R::REXP::Unknown->new(%args))
294             })
295 18         99 }
296              
297              
298             sub intsxp {
299 120     120 1 279 my ($object_info, $attributes) = (shift, shift);
300            
301 120 50       386 if ($object_info->{length} % 4 == 0) {
302             bind(count($object_info->{length}/4,
303             any_int32_na),
304             sub {
305 120 50   120   254 my @ints = @{shift or return};
  120         466  
306 120         457 my %args = (elements => [@ints]);
307 120 100       304 if ($attributes) {
308 18         53 $args{attributes} = $attributes
309             }
310 120         868 mreturn(Statistics::R::REXP::Integer->new(%args));
311             })
312 120         538 } 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 317 my ($object_info, $attributes) = (shift, shift);
321            
322 123 50       433 if ($object_info->{length} % 8 == 0) {
323             bind(count($object_info->{length}/8,
324             any_real64_na),
325             sub {
326 123 50   123   280 my @dbls = @{shift or return};
  123         523  
327 123         507 my %args = (elements => [@dbls]);
328 123 100       341 if ($attributes) {
329 18         54 $args{attributes} = $attributes
330             }
331 123         893 mreturn(Statistics::R::REXP::Double->new(%args));
332             })
333 123         581 } 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 58 my ($object_info, $attributes) = (shift, shift);
342            
343 21 50       80 if ($object_info->{length} % 16 == 0) {
344             bind(count($object_info->{length}/8,
345             any_real64_na),
346             sub {
347 21 50   21   40 my @dbls = @{shift or return};
  21         93  
348 21         46 my @cplx;
349 21         97 while (my ($re, $im) = splice(@dbls, 0, 2)) {
350 33 100 66     990 if (defined($re) && defined($im)) {
351 30         137 push(@cplx, Math::Complex::cplx($re, $im))
352             }
353             else {
354 3         13 push(@cplx, undef)
355             }
356             }
357 21         1424 my %args = (elements => [@cplx]);
358 21 100       64 if ($attributes) {
359 3         8 $args{attributes} = $attributes
360             }
361 21         211 mreturn(Statistics::R::REXP::Complex->new(%args));
362             })
363 21         112 } 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 35 my ($object_info, $attributes) = (shift, shift);
372            
373 14         36 my $dt_length = $object_info->{length},;
374 14 50       42 if ($dt_length) {
375             bind(\&any_uint32,
376             sub {
377 14   50 14   51 my $true_length = shift // return;
378 14         45 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       26 my ($elements, $padding) = @{shift or return};
  14         46  
386             my %args = (elements => [
387 14 100       50 map { $_ == 2 ? undef : $_ } @{$elements}
  31         97  
  14         36  
388             ]);
389 14 100       45 if ($attributes) {
390 1         3 $args{attributes} = $attributes
391             }
392 14         133 mreturn(Statistics::R::REXP::Logical->new(%args));
393             })
394 14         55 })
395 14         87 } else {
396 0         0 mreturn(Statistics::R::REXP::Logical->new);
397             }
398             }
399              
400              
401             sub rawsxp {
402 9     9 1 16 my $object_info = shift;
403              
404 9         21 my $dt_length = $object_info->{length},;
405 9 50       28 if ($dt_length) {
406             bind(\&any_uint32,
407             sub {
408 9   50 9   64 my $true_length = shift // return;
409 9         27 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       17 my ($elements, $padding) = @{shift or return};
  9         31  
417 9         52 mreturn(Statistics::R::REXP::Raw->new($elements));
418             })
419 9         34 })
420 9         48 } else {
421 0         0 mreturn(Statistics::R::REXP::Raw->new);
422             }
423             }
424              
425              
426             sub strsxp {
427 194     194 1 445 my ($object_info, $attributes) = (shift, shift);
428              
429 194         417 my $length = $object_info->{length};
430 194 100       478 if ($length) {
431             sub {
432 188     188   353 my $state = shift;
433 188         3640 my $end_at = $state->position + $length;
434              
435 188         1244 my @elements; # elements of the vector
436             my @characters; # characters in the current element
437 188         2892 while ($state->position < $end_at) {
438 3284         114028 my $ch = $state->at;
439 3284 100       72037 if (ord($ch)) {
440 2802         5568 push @characters, $ch;
441             } else {
442 482         1508 my $element = join('', @characters);
443 482 100       1101 if ($element eq "\xFF") {
444             ## NaStringRepresentation
445 3         8 push @elements, undef;
446             } else {
447             ## unescape real \xFF characters
448 479         911 $element =~ s/\xFF\xFF/\xFF/g;
449 479         1008 push @elements, $element;
450             }
451 482         1098 @characters = ();
452             }
453 3284         6983 $state = $state->next;
454             }
455            
456 188         7365 my %args = (elements => [@elements]);
457 188 100       522 if ($attributes) {
458 15         34 $args{attributes} = $attributes
459             }
460 188         1102 [ Statistics::R::REXP::Character->new(%args), $state ];
461             }
462 188         1325 } else {
463 6         41 mreturn(Statistics::R::REXP::Character->new);
464             }
465             }
466              
467              
468             sub vecsxp {
469 84     84 1 213 my ($object_info, $attributes) = (shift, shift);
470              
471 84         212 my $length = $object_info->{length};
472             sub {
473 84     84   178 my $state = shift;
474 84         1551 my $end_at = $state->position + $length;
475            
476 84         546 my @elements;
477 84         1253 while ($state->position < $end_at) {
478 231 50       4249 my $result = dt_sexp_data()->($state) or return;
479            
480 231         7219 push @elements, shift @$result;
481 231         2022 $state = shift @$result;
482             }
483 84         2226 my %args = (elements => [@elements]);
484 84 100       322 if ($attributes) {
485 30         83 $args{attributes} = $attributes
486             }
487 84         496 [ Statistics::R::REXP::List->new(%args), $state ];
488             }
489 84         516 }
490              
491              
492             sub expsxp {
493             bind(vecsxp(@_), sub {
494 15     15   31 my $list = shift;
495 15         279 my %args = (elements => $list->elements);
496 15         296 my $attributes = $list->attributes;
497 15 50       77 if ($attributes) {
498 0         0 $args{attributes} = $attributes
499             }
500 15         127 mreturn(Statistics::R::REXP::Expression->new(%args))
501             })
502 15     15 1 88 }
503              
504              
505             sub tagged_pairlist {
506 142     142 1 274 my $object_info = shift;
507              
508 142         328 my $length = $object_info->{length};
509 142 50       327 if ($length) {
510             sub {
511 142     142   257 my $state = shift;
512 142         2857 my $end_at = $state->position + $length;
513            
514 142         906 my @elements;
515 142         2129 while ($state->position < $end_at) {
516 356 50       2477 my $result = dt_sexp_data()->($state) or return;
517            
518 356         10436 my $value = shift @$result;
519 356         844 $state = shift @$result;
520              
521 356         2588 my $element = { value => $value };
522 356 100       1060 if ($object_info->{has_tags}) {
523 250 50       731 $result = dt_sexp_data()->($state) or return;
524 250         4314 my $tag = shift @$result;
525              
526 250 100       985 $element->{tag} = $tag unless $tag->is_null;
527 250         657 $state = shift @$result;
528             }
529            
530 356         8932 push @elements, $element;
531             }
532 142         1225 [ [ @elements ], $state ];
533             }
534 142         1173 } 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 210 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   297 my $list = shift or return;
551              
552 100         209 my @elements;
553             my @names;
554 100         269 foreach my $element (@$list) {
555 235         1516 my $tag = $element->{tag};
556 235         351 my $value = $element->{value};
557 235         405 push @elements, $value;
558 235 50       746 push @names, $tag ? $tag->name : '';
559             }
560              
561 100         1200 my %args = (elements => [ @elements ]);
562             ## if no element is tagged, then don't construct the
563             ## 'names' attribute
564 100 50       230 if (grep {exists $_->{tag}} @$list) {
  235         563  
565             $args{attributes} = {
566 100         429 names => Statistics::R::REXP::Character->new([ @names ])
567             };
568             }
569              
570 100         1179 mreturn(Statistics::R::REXP::List->new(%args))
571             })
572 100         370 }
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 103 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   138 my $list = shift or return;
588              
589 42         90 my @elements;
590             my @names;
591 42         108 foreach my $element (@$list) {
592 121         232 my $tag = $element->{tag};
593 121         173 my $value = $element->{value};
594 121         190 push @elements, $value;
595 121 100       300 push @names, $tag ? $tag->name : '';
596             }
597              
598 42         198 my %args = (elements => [ @elements ]);
599             ## if no element is tagged, then don't construct the
600             ## 'names' attribute
601 42 100       91 if (grep {exists $_->{tag}} @$list) {
  121         306  
602 5   50     33 $attributes //= {}; # initialize the hash
603 5         39 $attributes->{names} = Statistics::R::REXP::Character->new([ @names ]);
604             }
605 42 100       152 $args{attributes} = $attributes if $attributes;
606              
607 42         245 mreturn(Statistics::R::REXP::Language->new(%args))
608             })
609 42         127 }
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 2392 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   519 mreturn shift->[2]
653             })
654 161     161 1 2270 }
655              
656              
657             sub decode_int {
658 0     0 1 0 die 'TODO: implement'
659             }
660              
661              
662             sub decode {
663 161     161 1 139697 my $data = shift;
664 161 50 33     577 return error "Decode requires a scalar data or array reference" if ref $data && ref $data ne ref [];
665              
666 161         620 endianness('<');
667            
668 161         1089 my $result =
669             decode_sexp->(Statistics::R::IO::ParserState->new(data => $data));
670            
671 161 50       2114 if ($result) {
672 161         1798 my $state = $result->[1];
673 161 50       472 carp("remaining data: " . (scalar(@{$state->data}) - $state->position))
  0         0  
674             unless $state->eof;
675             }
676            
677 161         1548 $result;
678             }
679              
680              
681             1;
682              
683             __END__