File Coverage

blib/lib/Statistics/R/IO/Parser.pm
Criterion Covered Total %
statement 212 212 100.0
branch 85 114 74.5
condition 31 73 42.4
subroutine 65 65 100.0
pod 35 35 100.0
total 428 499 85.7


line stmt bran cond sub pod time code
1             package Statistics::R::IO::Parser;
2             # ABSTRACT: Functions for parsing R data files
3             $Statistics::R::IO::Parser::VERSION = '1.0002';
4 14     14   387889 use 5.010;
  14         47  
5 14     14   70 use strict;
  14         27  
  14         302  
6 14     14   71 use warnings FATAL => 'all';
  14         65  
  14         498  
7              
8 14     14   74 use Exporter 'import';
  14         26  
  14         1390  
9              
10             our @EXPORT = qw( );
11             our @EXPORT_OK = qw( endianness any_char char string
12             any_uint8 any_uint16 any_uint24 any_uint32 any_real32 any_real64 any_real64_na
13             uint8 uint16 uint24 uint32
14             any_int8 any_int16 any_int24 any_int32 any_int32_na
15             int8 int16 int24 int32
16             count with_count many_till seq choose mreturn error add_singleton get_singleton reserve_singleton bind );
17              
18             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ],
19             num => [ qw( any_uint8 any_uint16 any_uint24 any_uint32 any_int32_na any_real32 any_real64 any_real64_na uint8 uint16 uint24 uint32 ) ],
20             char => [ qw( any_char char string ) ],
21             combinator => [ qw( count with_count many_till seq choose mreturn bind ) ] );
22              
23              
24 14     14   81 use Scalar::Util qw(looks_like_number);
  14         25  
  14         664  
25 14     14   73 use Carp;
  14         28  
  14         29986  
26              
27             sub endianness {
28 55836     55836 1 76209 state $endianness = '>';
29 55836 50 100     148671 my $new_value = shift if @_ or return $endianness;
30 952   66     5353 $endianness = $new_value =~ /^[<>]$/ && $new_value || $endianness;
31             }
32              
33              
34             sub any_char {
35 277948     277948 1 349093 my $state = shift;
36              
37 277948 100 66     768689 return undef if !$state || $state->eof;
38            
39 277933         2016585 [$state->at, $state->next]
40             }
41              
42              
43             sub char {
44 7     7 1 2953 my $arg = shift;
45 7 100       27 die 'Must be a single-char argument: ' . $arg unless length($arg) == 1;
46            
47             sub {
48 16 50   16   82 my $state = shift or return;
49 16 100 100     43 return if $state->eof || $arg ne $state->at;
50            
51 6         142 [ $arg, $state->next ]
52             }
53 6         37 }
54              
55              
56             sub string {
57 1580     1580 1 3612 my $arg = shift;
58 1580 100 66     5340 die 'Must be a scalar argument: ' . $arg unless $arg && !ref($arg);
59 1579         3831 my $chars = count(length($arg), \&any_char);
60              
61             sub {
62 960 100   960   1451 my ($char_values, $state) = @{$chars->(@_) or return};
  960         1693  
63 959 100       1948 return unless join('', @{$char_values}) eq $arg;
  959         3058  
64 791         1945 [ $arg, $state ]
65             }
66 1579         7272 }
67              
68              
69             sub any_uint8 {
70 237063 100   237063 1 285292 my ($value, $state) = @{any_char @_ or return};
  237063         345336  
71            
72 237051         3018965 [ unpack('C', $value), $state ]
73             }
74              
75              
76             sub any_uint16 {
77 18 50   18 1 79 my ($value, $state) = @{count(2, \&any_uint8)->(@_) or return};
  18         48  
78            
79 18         45 [ unpack("S" . endianness, pack 'C2' => @{$value}),
  18         112  
80             $state ]
81             }
82              
83              
84             sub any_uint24 {
85 179 100   179 1 400 my ($value, $state) = @{count(3, \&any_uint8)->(@_) or return};
  179         590  
86            
87             [ unpack("L" . endianness,
88 175 100       498 pack(endianness eq '>' ? 'xC3' : 'C3x', @{$value})),
  175         1100  
89             $state ]
90             }
91              
92              
93             sub any_uint32 {
94 47385 100   47385 1 58711 my ($value, $state) = @{count(4, \&any_uint8)->(@_) or return};
  47385         92842  
95            
96 47379         103429 [ unpack("L" . endianness, pack 'C4' => @{$value}),
  47379         207783  
97             $state ]
98             }
99              
100              
101             sub uint8 {
102 169     169 1 426 my $arg = shift;
103 169 50 33     1274 die 'Argument must be a number 0-255: ' . $arg
      33        
104             unless looks_like_number($arg) && $arg <= 0x000000FF && $arg >= 0;
105            
106             sub {
107 169 50   169   247 my ($value, $state) = @{any_uint8 @_ or return};
  169         412  
108 169 100       568 return unless $arg == $value;
109            
110 167         521 [ $arg, $state ]
111             }
112 169         1192 }
113              
114              
115             sub uint16 {
116 8     8 1 805 my $arg = shift;
117 8 50 33     57 die 'Argument must be a number 0-65535: ' . $arg
      33        
118             unless looks_like_number($arg) && $arg <= 0x0000FFFF && $arg >= 0;
119            
120             sub {
121 8 50   8   15 my ($value, $state) = @{any_uint16 @_ or return};
  8         20  
122 8 100       23 return unless $arg == $value;
123            
124 6         31 [ $arg, $state ]
125             }
126 8         52 }
127              
128              
129             sub uint24 {
130 8     8 1 752 my $arg = shift;
131 8 50 33     54 die 'Argument must be a number 0-16777215: ' . $arg
      33        
132             unless looks_like_number($arg) && $arg <= 0x00FFFFFF && $arg >= 0;
133            
134             sub {
135 8 100   8   13 my ($value, $state) = @{any_uint24 @_ or return};
  8         16  
136 6 100       19 return unless $arg == $value;
137            
138 4         16 [ $arg, $state ]
139             }
140 8         39 }
141              
142              
143             sub uint32 {
144 1640     1640 1 3336 my $arg = shift;
145 1640 50 33     8309 die 'Argument must be a number 0-4294967295: ' . $arg
      33        
146             unless looks_like_number($arg) && $arg <= 0xFFFFFFFF && $arg >= 0;
147            
148             sub {
149 7526 100   7526   9953 my ($value, $state) = @{any_uint32 @_ or return};
  7526         13942  
150 7524 100       24257 return unless $arg == $value;
151            
152 1710         3969 [ $arg, $state ]
153             }
154 1640         5802 }
155              
156              
157             sub any_int8 {
158 4 50   4 1 28 my ($value, $state) = @{any_char @_ or return};
  4         8  
159            
160 4         64 [ unpack('c', $value), $state ]
161             }
162              
163              
164             sub any_int16 {
165 4 50   4 1 39 my ($value, $state) = @{any_uint16 @_ or return};
  4         11  
166            
167 4 100       13 $value |= 0x8000 if ($value >= 1<<15);
168 4         25 [ unpack('s', pack 's' => $value),
169             $state ]
170             }
171              
172              
173             sub any_int24 {
174 4 50   4 1 38 my ($value, $state) = @{any_uint24 @_ or return};
  4         10  
175            
176 4 100       11 $value |= 0xff800000 if ($value >= 1<<23);
177 4         22 [ unpack('l', pack 'l' => $value),
178             $state ]
179             }
180              
181              
182             sub any_int32 {
183 16630 100   16630 1 22887 my ($value, $state) = @{any_uint32 @_ or return};
  16630         29289  
184            
185 16628 100       38286 $value |= 0x80000000 if ($value >= 1<<31);
186 16628         52973 [ unpack('l', pack 'l' => $value),
187             $state ]
188             }
189              
190              
191             sub int8 {
192 2     2 1 24 my $arg = shift;
193 2 50 33     18 die 'Argument must be a number -128-127: ' . $arg
      33        
194             unless looks_like_number($arg) && $arg < 1<<7 && $arg >= -(1<<7);
195            
196             sub {
197 2 50   2   4 my ($value, $state) = @{any_int8 @_ or return};
  2         6  
198 2 50       8 return unless $arg == $value;
199            
200 2         8 [ $arg, $state ]
201             }
202 2         11 }
203              
204              
205             sub int16 {
206 2     2 1 41 my $arg = shift;
207 2 50 33     26 die 'Argument must be a number -32768-32767: ' . $arg
      33        
208             unless looks_like_number($arg) && $arg < 1<<15 && $arg >= -(1<<15);
209            
210             sub {
211 2 50   2   5 my ($value, $state) = @{any_int16 @_ or return};
  2         12  
212 2 50       6 return unless $arg == $value;
213            
214 2         25 [ $arg, $state ]
215             }
216 2         13 }
217              
218              
219             sub int24 {
220 2     2 1 40 my $arg = shift;
221 2 50 33     17 die 'Argument must be a number 0-16777215: ' . $arg
      33        
222             unless looks_like_number($arg) && $arg < 1<<23 && $arg >= -(1<<23);
223            
224             sub {
225 2 50   2   3 my ($value, $state) = @{any_int24 @_ or return};
  2         5  
226 2 50       6 return unless $arg == $value;
227            
228 2         11 [ $arg, $state ]
229             }
230 2         11 }
231              
232              
233             sub int32 {
234 1189     1189 1 2257 my $arg = shift;
235 1189 50 33     9780 die 'Argument must be a number -2147483648-2147483647: ' . $arg
      33        
236             unless looks_like_number($arg) && $arg < 1<<31 && $arg >= -(1<<31);
237            
238             sub {
239 2738 100   2738   3857 my ($value, $state) = @{any_int32 @_ or return};
  2738         5388  
240 2737 100       9675 return unless $arg == $value;
241            
242 121         314 [ $arg, $state ]
243             }
244 1189         6958 }
245              
246              
247             sub any_int32_na {
248             choose(&bind(int32(-2147483648),
249             sub {
250 119     119   440 mreturn(undef);
251 1186     1186 1 3893 }),
252             \&any_int32)
253             }
254              
255             my %na_real = ( '>' => [ uint32(0x7ff00000),
256             uint32(0x7a2) ],
257             '<' => [ uint32(0x7a2),
258             uint32(0x7ff00000) ]);
259              
260             sub any_real64_na {
261 1319         3039 choose(&bind(seq(@{$na_real{endianness()}}),
262             sub {
263 65     65   152 mreturn(undef);
264 1319     1319 1 3009 }),
265             \&any_real64)
266             }
267              
268              
269             sub any_real32 {
270 2 50   2 1 26 my ($value, $state) = @{count(4, \&any_uint8)->(@_) or return};
  2         9  
271            
272 2         8 [ unpack("f" . endianness, pack 'C4' => @{$value}),
  2         19  
273             $state ]
274             }
275              
276              
277             sub any_real64 {
278 5815 50   5815 1 8407 my ($value, $state) = @{count(8, \&any_uint8)->(@_) or return};
  5815         13287  
279            
280 5815         14279 [ unpack("d" . endianness, pack 'C8' => @{$value}),
  5815         25928  
281             $state ]
282             }
283              
284              
285             sub count {
286 70582     70582 1 114983 my ($n, $parser) = (shift, shift);
287             sub {
288 70015     70015   99580 my $state = shift;
289 70015         84381 my @value;
290              
291 70015         135839 for (1..$n) {
292 300716 100       1845413 my $result = $parser->($state) or return;
293              
294 300701         1135239 push @value, shift @$result;
295 300701         704470 $state = shift @$result;
296             }
297              
298 70000         914605 return [ [ @value ], $state ];
299             }
300 70582         262836 }
301              
302              
303             sub seq {
304 10675     10675 1 23041 my @parsers = @_;
305            
306             sub {
307 15234     15234   20669 my $state = shift;
308 15234         19737 my @value;
309              
310 15234         24515 foreach my $parser (@parsers) {
311 22407 100       51786 my $result = $parser->($state) or return;
312              
313 16585         147728 push @value, shift @$result;
314 16585         36479 $state = shift @$result;
315             }
316              
317 9412         68839 return [ [ @value ], $state ];
318             }
319 10675         54995 }
320              
321              
322             sub many_till {
323 1     1 1 3 my ($p, $end) = (shift, shift);
324 1 50 33     6 die "'bind' expects two arguments" unless $p && $end;
325            
326             sub {
327 2 50   2   1143 my $state = shift or return;
328 2         4 my @value;
329              
330 2         4 until ($end->($state)) {
331 3 100       88 my $result = $p->($state) or return;
332            
333 2         23 push @value, shift @$result;
334 2         6 $state = shift @$result;
335             }
336            
337 1         11 return [ [ @value ], $state ]
338             }
339 1         6 }
340              
341              
342             sub choose {
343 3294     3294 1 7829 my @parsers = @_;
344            
345             sub {
346 9403 50   9403   18402 my $state = shift or return;
347            
348 9403         14734 foreach my $parser (@parsers) {
349 18000         33418 my $result = $parser->($state);
350 18000 100       49253 return $result if $result;
351             }
352            
353 1         5 return;
354             }
355 3294         15265 }
356              
357              
358             sub mreturn {
359 50561     50561 1 170282 my $arg = shift;
360             sub {
361 50561     50561   352328 [ $arg, shift ]
362             }
363 50561         191561 }
364              
365              
366             sub error {
367 12     12 1 35 my $message = shift;
368             sub {
369 12     12   21 my $state = shift;
370 12         243 croak $message . " (at " . $state->position . ")";
371             }
372 12         61 }
373              
374              
375             sub add_singleton {
376 1883     1883 1 27824 my $singleton = shift;
377             sub {
378 1883     1883   6673 [ $singleton, shift->add_singleton($singleton) ]
379             }
380 1883         8358 }
381              
382              
383             sub get_singleton {
384 1632     1632 1 2743 my $ref_id = shift;
385             sub {
386 1632     1632   2873 my $state = shift;
387 1632         5027 [ $state->get_singleton($ref_id), $state ]
388             }
389 1632         6492 }
390              
391              
392             ## Preallocates a space for a singleton before running a given parser,
393             ## and then assigns the parser's value to the singleton.
394             sub reserve_singleton {
395              
396 53     53 1 118 my $p = shift;
397             &bind(
398             seq(
399             sub {
400 53     53   113 my $state = shift;
401 53         99 my $ref_id = scalar(@{$state->singletons});
  53         1062  
402 53         411 my $new_state = $state->add_singleton(undef);
403 53         622 [ $ref_id, $new_state ]
404             },
405             $p),
406             sub {
407 53     53   106 my ($ref_id, $value) = @{shift()};
  53         151  
408             sub {
409 53         128 my $state = shift;
410 53         1063 $state->singletons->[$ref_id] = $value;
411 53         1154 [ $value, $state ]
412             }
413 53         292 })
414 53         261 }
415              
416              
417             sub bind {
418 72336     72336 1 121451 my ($p1, $fp2) = (shift, shift);
419 72336 50 33     200999 die "'bind' expects two arguments" unless $p1 && $fp2;
420            
421             sub {
422 93806   50 93806   193992 my $v1 = $p1->(shift or return);
423 93785 100       243736 my ($value, $state) = @{$v1 or return};
  93785         193709  
424 85191         194253 $fp2->($value)->($state)
425             }
426 72336         259917 }
427              
428              
429             sub with_count {
430 4837 50 33 4837 1 19684 die "'bind' expects one or two arguments"
431             unless @_ and scalar(@_) <= 2;
432              
433 4837 100       11616 unshift(@_, \&any_uint32) if (scalar(@_) == 1);
434 4837         9350 my ($counter, $content) = (shift, shift);
435              
436             &bind($counter,
437             sub {
438 4827     4827   6633 my $n = shift;
439 4827         9715 count($n, $content)
440             })
441 4837         15067 }
442              
443              
444             1;
445              
446             __END__