File Coverage

blib/lib/PLON.pm
Criterion Covered Total %
statement 172 190 90.5
branch 107 124 86.2
condition 2 5 40.0
subroutine 32 33 96.9
pod 1 6 16.6
total 314 358 87.7


line stmt bran cond sub pod time code
1             package PLON;
2 12     12   303403 use 5.008005;
  12         49  
  12         514  
3 12     12   64 use strict;
  12         24  
  12         450  
4 12     12   92 use warnings FATAL => 'all';
  12         23  
  12         634  
5 12     12   62 use Scalar::Util qw(blessed reftype);
  12         21  
  12         1597  
6 12     12   7951 use parent qw(Exporter);
  12         2740  
  12         71  
7 12     12   586 use B;
  12         20  
  12         535  
8 12     12   33904 use Encode ();
  12         164985  
  12         266  
9 12     12   100 use Carp ();
  12         27  
  12         1731  
10              
11             our $VERSION = "0.08";
12              
13             our @EXPORT = qw(encode_plon decode_pson $_perl);
14              
15             our $INDENT;
16              
17             my $WS = qr{[ \t]*};
18              
19 1     1 0 16 sub encode_plon { PLON->new->encode(shift) }
20 1     1 0 4 sub decode_pson { PLON->new->decode(shift) }
21              
22             sub mk_accessor {
23 48     48 0 94 my ($pkg, $name) = @_;
24              
25 12     12   62 no strict 'refs';
  12         23  
  12         19819  
26 48         311 *{"${pkg}::${name}"} = sub {
27 6 100   6   29 my $enable = defined($_[1]) ? $_[1] : 1;
28 6 50       16 if ($enable) {
29 6         41 $_[0]->{$name} = 1;
30             } else {
31 0         0 $_[0]->{$name} = 0;
32             }
33 6         48 $_[0];
34 48         148 };
35 48         276 *{"${pkg}::get_${name}"} = sub {
36 137 100   137   1762 $_[0]->{$name} ? 1 : '';
37 48         130 };
38             }
39              
40             sub new {
41 25     25 1 20701 my $class = shift;
42 25         289 bless {
43             }, $class;
44             }
45              
46             mk_accessor(__PACKAGE__, $_) for qw(pretty ascii deparse canonical);
47              
48             sub encode {
49 30     30 0 68509 my ($self, $stuff) = @_;
50 30         71 local $INDENT = -1;
51 30         114 return $self->_encode($stuff);
52             }
53              
54             sub _encode {
55 73     73   126 my ($self, $value) = @_;
56 73         119 local $INDENT = $INDENT + 1;
57              
58 73         189 my $blessed = blessed($value);
59              
60 73 100       163 if (defined $blessed) {
61 3         15 'bless(' . $self->_encode_basic($value, 1) . ',' . $self->_encode_basic($blessed) . ')';
62             } else {
63 70         305 $self->_encode_basic($value);
64             }
65             }
66              
67             sub _encode_basic {
68 76     76   124 my ($self, $value, $blessing) = @_;
69              
70 76 100       180 if (not defined $value) {
71 2         10 return 'undef';
72             }
73              
74 74         239 my $reftype = reftype($value);
75 74 100       378 if (not defined $reftype) {
    100          
    100          
    100          
    100          
    100          
    50          
76 42         594 my $flags = B::svref_2object(\$value)->FLAGS;
77 42 100 66     279 return 0 + $value if $flags & (B::SVp_IOK | B::SVp_NOK) && $value * 0 == 0;
78              
79             # string
80 30 100       90 if ($self->{ascii}) {
81 2         10 $value =~ s/"/\\"/g;
82 2 50       13 if (Encode::is_utf8($value)) {
83 2         6 my $buf = '';
84 2         11 for (split //, $value) {
85 3 100       14 if ($_ =~ /\G[a-zA-Z0-9_ -]\z/) {
86 1         5 $buf .= Encode::encode_utf8($_);
87             } else {
88 2         12 $buf .= sprintf "\\x{%X}", ord $_;
89             }
90             }
91 2         11 $value = $buf;
92             } else {
93 0         0 $value = $value;
94             }
95 2         12 q{"} . $value . q{"};
96             } else {
97             #
98             # Here is the list of special characters from perlop.pod
99             #
100             # Sequence Note Description
101             # \t tab (HT, TAB)
102             # \n newline (NL)
103             # \r return (CR)
104             # \f form feed (FF)
105             # \b backspace (BS)
106             # \a alarm (bell) (BEL)
107             # \e escape (ESC)
108             # \x{263A} [1,8] hex char (example: SMILEY)
109             # \x1b [2,8] restricted range hex char (example: ESC)
110             # \N{name} [3] named Unicode character or character sequence
111             # \N{U+263D} [4,8] Unicode character (example: FIRST QUARTER MOON)
112             # \c[ [5] control char (example: chr(27))
113             # \o{23072} [6,8] octal char (example: SMILEY)
114             # \033 [7,8] restricted range octal char (example: ESC)
115             #
116 28         346 my %special_chars = (
117             qq{"} => q{\"},
118             qq{\t} => q{\t},
119             qq{\n} => q{\n},
120             qq{\r} => q{\r},
121             qq{\f} => q{\f},
122             qq{\b} => q{\b},
123             qq{\a} => q{\a},
124             qq{\e} => q{\e},
125             q{$} => q{\$},
126             q{@} => q{\@},
127             q{%} => q{\%},
128             q{\\} => q{\\\\},
129             );
130 28         230 $value =~ s/(.)/
131 43 100       152 if (exists($special_chars{$1})) {
132 11         37 $special_chars{$1};
133             } else {
134 32         109 $1
135             }
136             /gexs;
137 28 100       141 $value = Encode::is_utf8($value) ? Encode::encode_utf8($value) : $value;
138 28         216 q{"} . $value . q{"};
139             }
140             } elsif ($reftype eq 'SCALAR') {
141 3 100       11 if ($blessing) {
142 1         5 '\\(do {my $o=' . $self->_encode($$value) . '})';
143             } else {
144 2         12 '\\(' . $self->_encode($$value) . ')';
145             }
146             } elsif ($reftype eq 'REF') {
147 1         4 '\\(' . $self->_encode($$value) . ')';
148             } elsif ($reftype eq 'ARRAY') {
149 25         80 join('',
150             '[',
151             $self->_nl,
152 20         139 (map { $self->_indent(1) . $self->_encode($_) . "," . $self->_nl }
153             @$value),
154             $self->_indent,
155             ']',
156             );
157             } elsif ($reftype eq 'CODE') {
158 2 100       8 if ($self->get_deparse) {
159 1         9 require B::Deparse;
160 1 50       8 my $code = B::Deparse->new($self->get_pretty ? '' : '-si0')->coderef2text($value);
161 1         8 $code = "sub ${code}";
162 1 50       3 if ($self->get_pretty) {
163 0         0 my $indent = $self->_indent;
164 0         0 $code =~ s/^/$indent/gm;
165 0         0 $code;
166             } else {
167 1         6 $code =~ s/\n//g;
168 1         12 $code;
169             }
170             } else {
171 1         10 'sub { "DUMMY" }'
172             }
173             } elsif ($reftype eq 'HASH') {
174 5         22 my @keys = keys %$value;
175 5 100       22 if ($self->get_canonical) {
176 1         5 @keys = sort { $a cmp $b } @keys;
  2         5  
177             }
178              
179 7         20 join('',
180             '{',
181             $self->_nl,
182             (map {
183 5         21 $self->_indent(1) . $self->_encode($_)
184             . $self->_before_sp . '=>' . $self->_after_sp
185             . $self->_encode($value->{$_})
186             . "," . $self->_nl,
187             } @keys),
188             $self->_indent,
189             '}',
190             );
191             } elsif ($reftype eq 'GLOB') {
192 1         9 "\\(" . $$value . ")";
193             } else {
194 0         0 die "Unknown type: ${reftype}";
195             }
196             }
197              
198             sub _indent {
199 57     57   90 my ($self, $n) = @_;
200 57 100       137 if (not defined $n) { $n = 0 };
  25         35  
201 57 100       106 $self->get_pretty ? ' ' x ($INDENT+$n) : ''
202             }
203              
204             sub _nl {
205 57     57   85 my $self = shift;
206 57 100       164 $self->get_pretty ? "\n" : '',
207             }
208              
209             sub _before_sp {
210 7     7   11 my $self = shift;
211 7 100       18 $self->get_pretty ? " " : ''
212             }
213              
214             sub _after_sp {
215 7     7   11 my $self = shift;
216 7 100       15 $self->get_pretty ? " " : ''
217             }
218              
219             sub decode {
220 31     31 0 31459 my ($self, $src) = @_;
221 31         64 local $_ = $src;
222 31         127 return $self->_decode();
223             }
224              
225             sub _decode {
226 68     68   103 my ($self) = @_;
227              
228 68 100       2002 if (/\G$WS\{/gc) {
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
229 5         18 return $self->_decode_hash();
230             } elsif (/\G$WS\[/gc) {
231 18         61 return $self->_decode_array();
232             } elsif (/\G$WS"/gc) {
233 23         973 return $self->_decode_string();
234             } elsif (/\G${WS}undef/gc) {
235 2         12 return undef;
236             } elsif (/\G${WS}\\\(/gc) {
237 5         21 return $self->_decode_scalarref();
238             } elsif (/\G${WS}sub\s*\{/gc) {
239 2         7 return $self->_decode_code();
240             } elsif (/\G$WS"/gc) {
241 0         0 return $self->_decode_string;
242             } elsif (/\G$WS([0-9\.]+)/gc) {
243 8         44 return 0+$1;
244             } elsif (/\G${WS}bless\(/gc) {
245 3         13 return $self->_decode_object;
246             } elsif (/\G${WS}do \{my \$o=/gc) {
247 1         6 return $self->_decode_do;
248             } elsif (/\G$WS\*([a-zA-Z0-9_:]+)/gc) {
249 12     12   166 no strict 'refs';
  12         23  
  12         26118  
250 1         2 *{$1};
  1         14  
251             } else {
252 0         0 Carp::confess("Unexpected token: " . substr($_, pos, 9));
253             }
254             }
255              
256             sub _decode_hash {
257 5     5   9 my ($self) = @_;
258              
259 5         7 my %ret;
260 5         90 until (/\G$WS(,$WS)?\}/gc) {
261 5         17 my $k = $self->_decode();
262 5 50       177 /\G$WS=>$WS/gc
263             or _exception("Unexpected token in Hash");
264 5         12 my $v = $self->_decode();
265              
266 5         67 $ret{$k} = $v;
267              
268 5 100       61 /\G$WS,/gc
269             or last;
270             }
271 5         48 return \%ret;
272             }
273              
274             sub _decode_array {
275 18     18   28 my ($self) = @_;
276              
277 18         22 my @ret;
278 18         255 until (/\G$WS,?$WS\]/gc) {
279 15         48 my $term = $self->_decode();
280 14         390 push @ret, $term;
281             }
282 17         138 return \@ret;
283             }
284              
285             sub _decode_code {
286             # We can't decode coderef. Because it makes security issue.
287             # And, we can't detect end of code block.
288 2     2   335 Carp::confess("Cannot decode PLON contains CodeRef.");
289             }
290              
291             sub _decode_object {
292 3     3   7 my ($self) = @_;
293 3         15 my $body = $self->_decode; # class name
294 3 50       38 m!\G${WS},\s*!gc
295             or _exception("Missing comma after bless");
296 3         7 my $str = $self->_decode; # class name
297 3 50       102 m!\G${WS}\)!gc
298             or _exception("Missing closing paren after bless");
299 3         37 return bless($body, $str);
300             }
301              
302             sub _decode_scalarref {
303 5     5   11 my $self = shift;
304 5         17 my $value = $self->_decode();
305 5 50       30 /\G\s*\)/gc
306             or _exception("Missing closing paren after scalarref");
307 5         32 return \$value;
308             }
309              
310             # do {my $o=3}
311             sub _decode_do {
312 1     1   4 my $self = shift;
313 1         5 my $value = $self->_decode;
314 1 50       8 m!\G\}!gc
315             or _exception("Missing closing blace after `do {`");
316 1         9 return $value;
317             }
318              
319             sub _decode_string {
320 23     23   33 my $self = shift;
321              
322 23         32 my $ret;
323 23         67 until (/\G"/gc) {
324 39 100       691 if (/\G\\"/gc) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    50          
    100          
    100          
    50          
325 1         4 $ret .= q{"};
326             } elsif (/\G\\\$/gc) {
327 1         5 $ret .= qq{\$};
328             } elsif (/\G\\t/gc) {
329 1         5 $ret .= qq{\t};
330             } elsif (/\G\\n/gc) {
331 1         3 $ret .= qq{\n};
332             } elsif (/\G\\r/gc) {
333 1         4 $ret .= qq{\r};
334             } elsif (/\G\\f/gc) {
335 1         3 $ret .= qq{\f};
336             } elsif (/\G\\b/gc) {
337 1         25 $ret .= qq{\b};
338             } elsif (/\G\\a/gc) {
339 1         4 $ret .= qq{\a};
340             } elsif (/\G\\e/gc) {
341 1         4 $ret .= qq{\e};
342             } elsif (/\G\\$/gc) {
343 0         0 $ret .= qq{\$};
344             } elsif (/\G\\@/gc) {
345 1         3 $ret .= qq{\@};
346             } elsif (/\G\\%/gc) {
347 0         0 $ret .= qq{\%};
348             } elsif (/\G\\\\/gc) {
349 1         3 $ret .= qq{\\};
350             } elsif (/\G\\x\{([0-9a-fA-F]+)\}/gc) { # \x{5963}
351 1         8 $ret .= chr(hex $1);
352             } elsif (/\G([^"\\]+)/gc) {
353 27         110 $ret .= $1;
354             } else {
355 0         0 _exception("Unexpected EOF in string");
356             }
357             }
358             # If it's utf-8, it means the PLON encoded by ASCII mode.
359             # The PLON contains "\x{5963}". Then, we shouldn't decode the string.
360 23 100       154 return Encode::is_utf8($ret) ? $ret : Encode::decode_utf8($ret);
361             }
362              
363             sub _exception {
364              
365             # Leading whitespace
366 0     0     m/\G$WS/gc;
367              
368             # Context
369 0           my $context = 'Malformed PLON: ' . shift;
370 0 0         if (m/\G\z/gc) { $context .= ' before end of data' }
  0            
371             else {
372 0           my @lines = split "\n", substr($_, 0, pos);
373 0   0       $context .= ' at line ' . @lines . ', offset ' . length(pop @lines || '');
374             }
375              
376 0           die "$context\n";
377             }
378              
379             1;
380             __END__