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.0';
4 12     12   135098 use 5.010;
  12         29  
5 12     12   40 use strict;
  12         17  
  12         232  
6 12     12   35 use warnings FATAL => 'all';
  12         57  
  12         335  
7              
8 12     12   37 use Exporter 'import';
  12         17  
  12         1032  
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 12     12   40 use Scalar::Util qw(looks_like_number);
  12         15  
  12         736  
25 12     12   39 use Carp;
  12         13  
  12         23326  
26              
27             sub endianness {
28 22874     22874 1 18138 state $endianness = '>';
29 22874 50 100     53430 my $new_value = shift if @_ or return $endianness;
30 486   66     2882 $endianness = $new_value =~ /^[<>]$/ && $new_value || $endianness;
31             }
32              
33              
34             sub any_char {
35 113148     113148 1 77516 my $state = shift;
36              
37 113148 100 66     283132 return undef if !$state || $state->eof;
38            
39 113133         648670 [$state->at, $state->next]
40             }
41              
42              
43             sub char {
44 7     7 1 1568 my $arg = shift;
45 7 100       23 die 'Must be a single-char argument: ' . $arg unless length($arg) == 1;
46            
47             sub {
48 16 50   16   49 my $state = shift or return;
49 16 100 100     33 return if $state->eof || $arg ne $state->at;
50            
51 6         131 [ $arg, $state->next ]
52             }
53 6         31 }
54              
55              
56             sub string {
57 648     648 1 1200 my $arg = shift;
58 648 100 66     2120 die 'Must be a scalar argument: ' . $arg unless $arg && !ref($arg);
59 647         1187 my $chars = count(length($arg), \&any_char);
60              
61             sub {
62 388 100   388   305 my ($char_values, $state) = @{$chars->(@_) or return};
  388         508  
63 387 100       514 return unless join('', @{$char_values}) eq $arg;
  387         1092  
64 325         591 [ $arg, $state ]
65             }
66 647         2342 }
67              
68              
69             sub any_uint8 {
70 96531 100   96531 1 63568 my ($value, $state) = @{any_char @_ or return};
  96531         104331  
71            
72 96519         979524 [ unpack('C', $value), $state ]
73             }
74              
75              
76             sub any_uint16 {
77 18 50   18 1 57 my ($value, $state) = @{count(2, \&any_uint8)->(@_) or return};
  18         35  
78            
79 18         70 [ unpack("S" . endianness, pack 'C2' => @{$value}),
  18         87  
80             $state ]
81             }
82              
83              
84             sub any_uint24 {
85 179 100   179 1 210 my ($value, $state) = @{count(3, \&any_uint8)->(@_) or return};
  179         432  
86            
87             [ unpack("L" . endianness,
88 175 100       780 pack(endianness eq '>' ? 'xC3' : 'C3x', @{$value})),
  175         864  
89             $state ]
90             }
91              
92              
93             sub any_uint32 {
94 18989 100   18989 1 13768 my ($value, $state) = @{count(4, \&any_uint8)->(@_) or return};
  18989         27546  
95            
96 18983         61033 [ unpack("L" . endianness, pack 'C4' => @{$value}),
  18983         65309  
97             $state ]
98             }
99              
100              
101             sub uint8 {
102 169     169 1 286 my $arg = shift;
103 169 50 33     1234 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   143 my ($value, $state) = @{any_uint8 @_ or return};
  169         287  
108 169 100       418 return unless $arg == $value;
109            
110 167         398 [ $arg, $state ]
111             }
112 169         940 }
113              
114              
115             sub uint16 {
116 8     8 1 619 my $arg = shift;
117 8 50 33     55 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   7 my ($value, $state) = @{any_uint16 @_ or return};
  8         12  
122 8 100       17 return unless $arg == $value;
123            
124 6         20 [ $arg, $state ]
125             }
126 8         34 }
127              
128              
129             sub uint24 {
130 8     8 1 608 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   7 my ($value, $state) = @{any_uint24 @_ or return};
  8         10  
136 6 100       17 return unless $arg == $value;
137            
138 4         12 [ $arg, $state ]
139             }
140 8         35 }
141              
142              
143             sub uint32 {
144 700     700 1 1243 my $arg = shift;
145 700 50 33     3602 die 'Argument must be a number 0-4294967295: ' . $arg
      33        
146             unless looks_like_number($arg) && $arg <= 0xFFFFFFFF && $arg >= 0;
147            
148             sub {
149 3174 100   3174   2392 my ($value, $state) = @{any_uint32 @_ or return};
  3174         4093  
150 3172 100       8742 return unless $arg == $value;
151            
152 714         1286 [ $arg, $state ]
153             }
154 700         1820 }
155              
156              
157             sub any_int8 {
158 4 50   4 1 21 my ($value, $state) = @{any_char @_ or return};
  4         7  
159            
160 4         54 [ unpack('c', $value), $state ]
161             }
162              
163              
164             sub any_int16 {
165 4 50   4 1 29 my ($value, $state) = @{any_uint16 @_ or return};
  4         6  
166            
167 4 100       9 $value |= 0x8000 if ($value >= 1<<15);
168 4         18 [ unpack('s', pack 's' => $value),
169             $state ]
170             }
171              
172              
173             sub any_int24 {
174 4 50   4 1 28 my ($value, $state) = @{any_uint24 @_ or return};
  4         7  
175            
176 4 100       10 $value |= 0xff800000 if ($value >= 1<<23);
177 4         16 [ unpack('l', pack 'l' => $value),
178             $state ]
179             }
180              
181              
182             sub any_int32 {
183 6504 100   6504 1 5331 my ($value, $state) = @{any_uint32 @_ or return};
  6504         7631  
184            
185 6502 100       12889 $value |= 0x80000000 if ($value >= 1<<31);
186 6502         14494 [ unpack('l', pack 'l' => $value),
187             $state ]
188             }
189              
190              
191             sub int8 {
192 2     2 1 19 my $arg = shift;
193 2 50 33     17 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   3 my ($value, $state) = @{any_int8 @_ or return};
  2         4  
198 2 50       5 return unless $arg == $value;
199            
200 2         7 [ $arg, $state ]
201             }
202 2         9 }
203              
204              
205             sub int16 {
206 2     2 1 31 my $arg = shift;
207 2 50 33     18 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   2 my ($value, $state) = @{any_int16 @_ or return};
  2         4  
212 2 50       4 return unless $arg == $value;
213            
214 2         7 [ $arg, $state ]
215             }
216 2         8 }
217              
218              
219             sub int24 {
220 2     2 1 30 my $arg = shift;
221 2 50 33     19 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   2 my ($value, $state) = @{any_int24 @_ or return};
  2         4  
226 2 50       5 return unless $arg == $value;
227            
228 2         6 [ $arg, $state ]
229             }
230 2         13 }
231              
232              
233             sub int32 {
234 521     521 1 808 my $arg = shift;
235 521 50 33     4089 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 1202 100   1202   1076 my ($value, $state) = @{any_int32 @_ or return};
  1202         1711  
240 1201 100       3357 return unless $arg == $value;
241            
242 57         101 [ $arg, $state ]
243             }
244 521         2309 }
245              
246              
247             sub any_int32_na {
248             choose(&bind(int32(-2147483648),
249             sub {
250 55     55   125 mreturn(undef);
251 518     518 1 1286 }),
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 575         922 choose(&bind(seq(@{$na_real{endianness()}}),
262             sub {
263 33     33   65 mreturn(undef);
264 575     575 1 890 }),
265             \&any_real64)
266             }
267              
268              
269             sub any_real32 {
270 2 50   2 1 19 my ($value, $state) = @{count(4, \&any_uint8)->(@_) or return};
  2         6  
271            
272 2         9 [ unpack("f" . endianness, pack 'C4' => @{$value}),
  2         13  
273             $state ]
274             }
275              
276              
277             sub any_real64 {
278 2459 50   2459 1 1976 my ($value, $state) = @{count(8, \&any_uint8)->(@_) or return};
  2459         4134  
279            
280 2459         8703 [ unpack("d" . endianness, pack 'C8' => @{$value}),
  2459         7845  
281             $state ]
282             }
283              
284              
285             sub count {
286 28452     28452 1 27441 my ($n, $parser) = (shift, shift);
287             sub {
288 28213     28213   20825 my $state = shift;
289 28213         17911 my @value;
290              
291 28213         45058 for (1..$n) {
292 121896 100       500231 my $result = $parser->($state) or return;
293              
294 121881         355334 push @value, shift @$result;
295 121881         213030 $state = shift @$result;
296             }
297              
298 28198         227566 return [ [ @value ], $state ];
299             }
300 28452         84561 }
301              
302              
303             sub seq {
304 4169     4169 1 5959 my @parsers = @_;
305            
306             sub {
307 6084     6084   4650 my $state = shift;
308 6084         4093 my @value;
309              
310 6084         6464 foreach my $parser (@parsers) {
311 9039 100       15140 my $result = $parser->($state) or return;
312              
313 6573         53708 push @value, shift @$result;
314 6573         10463 $state = shift @$result;
315             }
316              
317 3618         18080 return [ [ @value ], $state ];
318             }
319 4169         16300 }
320              
321              
322             sub many_till {
323 1     1 1 2 my ($p, $end) = (shift, shift);
324 1 50 33     7 die "'bind' expects two arguments" unless $p && $end;
325            
326             sub {
327 2 50   2   973 my $state = shift or return;
328 2         3 my @value;
329              
330 2         3 until ($end->($state)) {
331 3 100       63 my $result = $p->($state) or return;
332            
333 2         21 push @value, shift @$result;
334 2         4 $state = shift @$result;
335             }
336            
337 1         10 return [ [ @value ], $state ]
338             }
339 1         4 }
340              
341              
342             sub choose {
343 1416     1416 1 2017 my @parsers = @_;
344            
345             sub {
346 4013 50   4013   5906 my $state = shift or return;
347            
348 4013         3995 foreach my $parser (@parsers) {
349 7676         9604 my $result = $parser->($state);
350 7676 100       19722 return $result if $result;
351             }
352            
353 1         4 return;
354             }
355 1416         5088 }
356              
357              
358             sub mreturn {
359 19995     19995 1 54766 my $arg = shift;
360             sub {
361 19995     19995   44079 [ $arg, shift ]
362             }
363 19995         62534 }
364              
365              
366             sub error {
367 12     12 1 23 my $message = shift;
368             sub {
369 12     12   13 my $state = shift;
370 12         217 croak $message . " (at " . $state->position . ")";
371             }
372 12         46 }
373              
374              
375             sub add_singleton {
376 709     709 1 10658 my $singleton = shift;
377             sub {
378 709     709   1912 [ $singleton, shift->add_singleton($singleton) ]
379             }
380 709         2614 }
381              
382              
383             sub get_singleton {
384 578     578 1 612 my $ref_id = shift;
385             sub {
386 578     578   588 my $state = shift;
387 578         1414 [ $state->get_singleton($ref_id), $state ]
388             }
389 578         1807 }
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 21     21 1 29 my $p = shift;
397             &bind(
398             seq(
399             sub {
400 21     21   28 my $state = shift;
401 21         23 my $ref_id = scalar(@{$state->singletons});
  21         412  
402 21         119 my $new_state = $state->add_singleton(undef);
403 21         197 [ $ref_id, $new_state ]
404             },
405             $p),
406             sub {
407 21     21   31 my ($ref_id, $value) = @{shift()};
  21         40  
408             sub {
409 21         29 my $state = shift;
410 21         418 $state->singletons->[$ref_id] = $value;
411 21         144 [ $value, $state ]
412             }
413 21         150 })
414 21         87 }
415              
416              
417             sub bind {
418 29700     29700 1 26287 my ($p1, $fp2) = (shift, shift);
419 29700 50 33     83762 die "'bind' expects two arguments" unless $p1 && $fp2;
420            
421             sub {
422 37850   50 37850   64363 my $v1 = $p1->(shift or return);
423 37829 100       71993 my ($value, $state) = @{$v1 or return};
  37829         63061  
424 34169         65177 $fp2->($value)->($state)
425             }
426 29700         80495 }
427              
428              
429             sub with_count {
430 1793 50 33 1793 1 6886 die "'bind' expects one or two arguments"
431             unless @_ and scalar(@_) <= 2;
432              
433 1793 100       3161 unshift(@_, \&any_uint32) if (scalar(@_) == 1);
434 1793         1837 my ($counter, $content) = (shift, shift);
435              
436             &bind($counter,
437             sub {
438 1783     1783   1687 my $n = shift;
439 1783         2415 count($n, $content)
440             })
441 1793         4098 }
442              
443              
444             1;
445              
446             __END__