File Coverage

blib/lib/BERT/Decoder.pm
Criterion Covered Total %
statement 130 141 92.2
branch 45 58 77.5
condition n/a
subroutine 24 25 96.0
pod 2 19 10.5
total 201 243 82.7


line stmt bran cond sub pod time code
1             package BERT::Decoder;
2 6     6   34 use strict;
  6         10  
  6         188  
3 6     6   31 use warnings;
  6         9  
  6         129  
4              
5 6     6   93 use 5.008;
  6         21  
  6         221  
6              
7 6     6   32 use Carp 'croak';
  6         11  
  6         1924  
8 6     6   3263 use BERT::Constants;
  6         13  
  6         487  
9 6     6   2778 use BERT::Types;
  6         16  
  6         11951  
10              
11             sub new {
12 33     33 1 51 my $class = shift;
13 33         120 return bless { }, $class;
14             }
15              
16             sub decode {
17 33     33 1 59 my ($self, $bert) = @_;
18              
19 33         150 (my $magic, $bert) = unpack('Ca*', $bert);
20              
21 33 50       108 croak sprintf('Bad magic number. Expected %d found %d', MAGIC_NUMBER, $magic)
22             unless MAGIC_NUMBER == $magic;
23              
24 33         83 return $self->extract_any($bert);
25             }
26              
27             sub extract_any {
28 33     33 0 54 my ($self, $bert) = @_;
29              
30 33         90 (my $value, $bert) = $self->read_any($bert);
31              
32 33 100       121 $value = $self->extract_complex_type($value)
33             if ref $value eq 'BERT::Tuple';
34              
35 33 50       124 return [ $value, $self->extract_any($bert) ] if $bert;
36 33         158 return $value;
37             }
38              
39             sub extract_complex_type {
40 9     9 0 14 my ($self, $tuple) = @_;
41              
42 9         10 my @array = @{ $tuple->value };
  9         25  
43 9 100       33 return $tuple unless $array[0] eq 'bert';
44              
45 8 100       25 if ($array[1] eq 'nil') {
    100          
    100          
    100          
    100          
    50          
46 1         3 return undef;
47             } elsif ($array[1] eq 'true') {
48 1         9 return BERT::Boolean->true;
49             } elsif ($array[1] eq 'false') {
50 1         4 return BERT::Boolean->false;
51             } elsif ($array[1] eq 'dict') {
52 3         51 my @dict = map(@{ $_->value }, @{ $array[2] });
  3         18  
  3         9  
53              
54             # Someday I should add an option to allow hashref to be returned instead
55 3         20 return BERT::Dict->new(\@dict);
56             } elsif ($array[1] eq 'time') {
57 1         4 my ($megasec, $sec, $microsec) = @array[2, 3, 4];
58 1         10 return BERT::Time->new($megasec * 1_000_000 + $sec, $microsec);
59             } elsif ($array[1] eq 'regex') {
60 1         3 my ($source, $options) = @array[2, 3];
61 1         3 my $opt = '';
62 1         2 for (@{ $options }) {
  1         3  
63 1 50       3 if ($_ eq 'caseless') { $opt .= 'i' }
  1 0       4  
    0          
    0          
64 0         0 elsif ($_ eq 'dotall') { $opt .= 's' }
65 0         0 elsif ($_ eq 'extended') { $opt .= 'x' }
66 0         0 elsif ($_ eq 'multiline') { $opt .= 'm' }
67             }
68 1         156 return eval "qr/$source/$opt";
69             } else {
70 0         0 croak "Unknown complex type $array[1]";
71             }
72             }
73              
74             sub read_any {
75 80     80 0 110 my ($self, $bert) = @_;
76 80         74 my $value;
77              
78 80         230 (my $type, $bert) = unpack('Ca*', $bert);
79              
80 80 100       437 if (SMALL_INTEGER_EXT == $type) { return $self->read_small_integer($bert) }
  8 100       24  
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
81 7         22 elsif (INTEGER_EXT == $type) { return $self->read_integer($bert) }
82 2         11 elsif (FLOAT_EXT == $type) { return $self->read_float($bert) }
83 25         130 elsif (ATOM_EXT == $type) { return $self->read_atom($bert) }
84 12         35 elsif (SMALL_TUPLE_EXT == $type) { return $self->read_small_tuple($bert) }
85 0         0 elsif (LARGE_TUPLE_EXT == $type) { return $self->read_large_tuple($bert) }
86 2         38 elsif (NIL_EXT == $type) { return $self->read_nil($bert) }
87 3         50 elsif (STRING_EXT == $type) { return $self->read_string($bert) }
88 7         27 elsif (LIST_EXT == $type) { return $self->read_list($bert) }
89 10         29 elsif (BINARY_EXT == $type) { return $self->read_binary($bert) }
90 3         11 elsif (SMALL_BIG_EXT == $type) { return $self->read_small_big($bert) }
91 1         6 elsif (LARGE_BIG_EXT == $type) { return $self->read_large_big($bert) }
92 0         0 else { croak "Unknown type $type" }
93             }
94              
95             sub read_small_integer {
96 8     8 0 11 my ($self, $bert) = @_;
97 8         23 (my $value, $bert) = unpack('Ca*', $bert);
98 8         24 return ($value, $bert);
99             }
100              
101             sub read_integer {
102 7     7 0 28 my ($self, $bert) = @_;
103              
104             # This should have been unpack('l>a*',...) only and not have extra unpack('l',...)
105             # but I don't want to require perl >= v5.10
106 7         22 (my $value, $bert) = unpack('Na*', $bert);
107 7         24 $value = unpack('l', pack('L', $value));
108 7         23 return ($value, $bert);
109             }
110              
111             sub read_float {
112 2     2 0 5 my ($self, $bert) = @_;
113 2         10 (my $value, $bert) = unpack('Z31a*', $bert);
114 2         13 return ($value, $bert);
115             }
116              
117             sub read_atom {
118 25     25 0 123 my ($self, $bert) = @_;
119 25         63 (my $len, $bert) = unpack('na*', $bert);
120 25         87 (my $value, $bert) = unpack("a$len a*", $bert);
121 25         99 $value = BERT::Atom->new($value);
122 25         118 return ($value, $bert);
123             }
124              
125             sub read_small_tuple {
126 12     12 0 16 my ($self, $bert) = @_;
127 12         27 (my $len, $bert) = unpack('Ca*', $bert);
128 12         37 (my $value, $bert) = $self->read_array($bert, $len, []);
129 12         56 $value = BERT::Tuple->new($value);
130 12         36 return ($value, $bert);
131             }
132              
133             sub read_large_tuple {
134 0     0 0 0 my ($self, $bert) = @_;
135 0         0 (my $len, $bert) = unpack('Na*', $bert);
136 0         0 (my $value, $bert) = $self->read_array($bert, $len, []);
137 0         0 $value = BERT::Tuple->new($value);
138 0         0 return ($value, $bert);
139             }
140              
141             sub read_nil {
142 2     2 0 5 my ($self, $bert) = @_;
143 2         5 my $value = [];
144 2         8 return ($value, $bert);
145             }
146              
147             sub read_string {
148 3     3 0 4 my ($self, $bert) = @_;
149 3         14 (my $len, $bert) = unpack('na*', $bert);
150 3         23 my @values = unpack("C$len a*", $bert);
151 3         8 $bert = pop @values;
152 3         6 my $value = \@values;
153 3         13 return ($value, $bert);
154             }
155              
156             sub read_list {
157 7     7 0 13 my ($self, $bert) = @_;
158 7         20 (my $len, $bert) = unpack('Na*', $bert);
159 7         24 (my $value, $bert) = $self->read_array($bert, $len, []);
160 7         31 (my $type, $bert) = unpack('Ca*', $bert);
161 7 50       21 croak 'Lists with non NIL tails are not supported'
162             unless NIL_EXT == $type;
163 7         22 return ($value, $bert);
164             }
165              
166             sub read_binary {
167 10     10 0 15 my ($self, $bert) = @_;
168 10         33 (my $len, $bert) = unpack('Na*', $bert);
169 10         39 (my $value, $bert) = unpack("a$len a*", $bert);
170 10         37 return ($value, $bert);
171             }
172              
173             sub read_small_big {
174 3     3 0 9 my ($self, $bert) = @_;
175 3         11 (my $len, $bert) = unpack('Ca*', $bert);
176 3         10 (my $value, $bert) = $self->read_bigint($bert, $len);
177 3         15 return ($value, $bert);
178             }
179              
180             sub read_large_big {
181 1     1 0 2 my ($self, $bert) = @_;
182 1         6 (my $len, $bert) = unpack('Na*', $bert);
183 1         4 (my $value, $bert) = $self->read_bigint($bert, $len);
184 1         8 return ($value, $bert);
185             }
186              
187             sub read_bigint {
188 4     4 0 8 my $self = shift;
189 4         8 my ($bert, $len) = @_;
190              
191 4         82 my($sign, @values) = unpack("CC$len a*", $bert);
192 4         14 $bert = pop @values;
193              
194 4         1843 require Math::BigInt;
195 4         34148 my $i = Math::BigInt->new(0);
196 4         32253 my $value = 0;
197              
198 4         11 foreach my $item (@values) {
199 274         518194 $value += $item * 256 ** $i++;
200             }
201              
202 4 100       4559 $value->bneg() if $sign != 0;
203              
204 4         39 return ($value, $bert);
205             }
206              
207             sub read_array {
208 66     66 0 74 my $self = shift;
209 66         86 my ($bert, $len, $array) = @_;
210              
211 66 100       105 if ($len > 0) {
212 47         126 (my $value, $bert) = $self->read_any($bert);
213 47         77 return $self->read_array($bert, $len - 1, [@{ $array }, $value]);
  47         159  
214             } else {
215 19         72 return ($array, $bert);
216             }
217             }
218              
219             1;
220              
221             __END__