File Coverage

blib/lib/JSON5/Parser.pm
Criterion Covered Total %
statement 204 204 100.0
branch 73 74 98.6
condition 33 36 91.6
subroutine 32 32 100.0
pod 0 2 0.0
total 342 348 98.2


line stmt bran cond sub pod time code
1             package JSON5::Parser;
2 11     11   754 use strict;
  11         16  
  11         248  
3 11     11   1014 use warnings;
  11         723  
  11         1649  
4 11     11   2398 use utf8;
  11         50  
  11         45  
5              
6 11     11   241 use Carp qw/croak/;
  11         9  
  11         538  
7 11     11   38 use JSON::PP;
  11         11  
  11         428  
8 11     11   4996 use Encode;
  11         67538  
  11         2304  
9              
10             our $ROOT;
11             our $POINTER;
12              
13             sub new {
14 11     11 0 175 my $class = shift;
15             return bless +{
16             utf8 => 0,
17             allow_nonref => 0,
18             max_size => 0,
19 12 100   12   54 inflate_boolean => sub { $_[0] eq 'true' ? JSON::PP::true : JSON::PP::false },
20 2     2   4 inflate_nan => sub { 0+'NaN' },
21 7     7   6 inflate_null => sub { undef },
22 6 100   6   18 inflate_infinity => sub { $_[0] eq '+' ? 0+'Inf' : 0+'-Inf' },
23 11         195 } => $class;
24             }
25              
26             # define accessors
27             BEGIN {
28             # boolean accessors
29 11     11   22 for my $attr (qw/utf8 allow_nonref/) {
30             my $attr_accessor = sub {
31 19     19   27 my $self = shift;
32 19 100       97 $self->{$attr} = @_ ? shift : 1;
33 19         50 return $self;
34 22         50 };
35             my $attr_getter = sub {
36 4     4   5 my $self = shift;
37 4         17 return $self->{$attr};
38 22         64 };
39              
40 11     11   56 no strict qw/refs/;
  11         10  
  11         1235  
41 22         20 *{"$attr"} = $attr_accessor;
  22         62  
42 22         19 *{"get_$attr"} = $attr_getter;
  22         74  
43             }
44              
45             # value accessors
46 11         16 for my $attr (qw/max_size inflate_boolean inflate_nan inflate_null inflate_infinity/) {
47             my $attr_accessor = sub {
48 6     6   7 my $self = shift;
49 6 100       23 $self->{$attr} = shift if @_;
50 6         14 return $self;
51 55         124 };
52             my $attr_getter = sub {
53 3     3   5 my $self = shift;
54 3         12 return $self->{$attr};
55 55         110 };
56              
57 11     11   39 no strict qw/refs/;
  11         11  
  11         455  
58 55         42 *{"$attr"} = $attr_accessor;
  55         128  
59 55         39 *{"get_$attr"} = $attr_getter;
  55         565  
60             }
61             }
62              
63             sub parse {
64 110     110 0 56752 my ($self, $content) = @_;
65 110 100       268 if (my $max_size = $self->{max_size}) {
66 11     11   46 use bytes;
  11         14  
  11         42  
67 2         1 my $bytes = length $content;
68 2 100       179 $bytes <= $max_size
69             or croak sprintf 'attempted decode of JSON5 text of %s bytes size, but max_size is set to %s', $bytes, $max_size;
70             }
71 109 100       265 if ($self->{utf8}) {
72 83         167 $content = Encode::decode_utf8($content);
73             }
74              
75             # normalize linefeed
76 109         1522 $content =~ s!\r\n?!\n!mg;
77              
78 109         107 local $ROOT;
79 109         128 local $POINTER = \$ROOT;
80              
81 109         238 $self->_parse() for $content;
82              
83 95         263 return $ROOT;
84             }
85              
86             sub _parse {
87 109     109   109 my $self = shift;
88              
89 109         161 $self->_parse_value();
90 109 100       401 return if m!\G(?:\s*|//.*$|/\*.*?\*/)*\z!msgc;
91 14         25 $self->_error('Syntax Error');
92             }
93              
94 2436     2436   6241 sub _skip_whitespace { /\G\s*/msgc }
95 1083 100   1083   4431 sub _skip_comments { m!\G//.*$!mgc || m!\G/\*.*?\*/!msgc }
96              
97             sub _parse_value {
98 364     364   269 my $self = shift;
99              
100             # skip
101 364   100     377 1 while $self->_skip_whitespace() || $self->_skip_comments();
102              
103 364   100     633 my $allow_nonref = $self->{allow_nonref} || $POINTER != \$ROOT;
104              
105 364 100       433 if ($self->_parse_object_or_array()) {
    50          
106 112         160 return 1;
107             }
108             elsif ($allow_nonref) {
109 252 100       289 if ($self->_parse_number()) {
    100          
    100          
    100          
110 122         162 return 1;
111             }
112             elsif ($self->_parse_boolean()) {
113 12         22 return 1;
114             }
115             elsif ($self->_parse_string()) {
116 90         153 return 1;
117             }
118             elsif (/\Gnull/mgc) {
119 7         14 ${$POINTER} = $self->{inflate_null}->();
  7         35  
120 7         11 return 1;
121             }
122             }
123              
124 21         21 return;
125             }
126              
127             sub _parse_object_or_array {
128 364     364   268 my $self = shift;
129              
130 364 100       708 if (/\G\{/mgc) {
    100          
131 80         74 local $POINTER = ${$POINTER} = {};
  80         89  
132 80         108 return $self->_parse_object_kv();
133             }
134             elsif (/\G\[/mgc) {
135 45         69 local $POINTER = ${$POINTER} = [];
  45         56  
136 45         71 return $self->_parse_array_value();
137             }
138              
139 239         505 return;
140             }
141              
142             sub _parse_object_kv {
143 153     153   106 my $self = shift;
144              
145             # skip
146 153   100     165 1 while $self->_skip_whitespace() || $self->_skip_comments();
147              
148             # is last?
149 153 100       258 if (/\G\}/mgc) {
150 17         44 return 1;
151             }
152              
153             # parse key
154 136         85 my $key; {
155 136         97 local $POINTER = \$key;
  136         104  
156 136 100 66     155 if (!$self->_parse_string() && !$self->_parse_identifier()) {
157 2         5 return;
158             }
159             }
160              
161             # skip
162 134   66     164 1 while $self->_skip_whitespace() || $self->_skip_comments();
163              
164             # parse object key sep
165 134 100       264 unless (/\G\:/mgc) {
166 1         3 return;
167             }
168              
169             # parse value
170 133         95 my $value; {
171 133         79 local $POINTER = \$value;
  133         109  
172 133 100       178 if (!$self->_parse_value()) {
173 3         32 return;
174             }
175             }
176              
177             # set value
178 130         94 my $hash = $POINTER;
179 130         226 $hash->{$key} = $value;
180              
181             # skip
182 130   100     169 1 while $self->_skip_whitespace() || $self->_skip_comments();
183              
184             # is last?
185 130 100       286 if (/\G\}/mgc) {
    100          
186 55         90 return 1;
187             }
188             elsif (/\G,/mgc) {
189 73         128 return $self->_parse_object_kv;
190             }
191              
192 2         5 return;
193             }
194              
195             sub _parse_array_value {
196 141     141   101 my $self = shift;
197              
198             # skip
199 141   100     147 1 while $self->_skip_whitespace() || $self->_skip_comments();
200              
201             # is last?
202 141 100       230 if (/\G\]/mgc) {
203 19         43 return 1;
204             }
205              
206             # parse value
207 122         76 my $value; {
208 122         83 local $POINTER = \$value;
  122         104  
209 122 100       143 if (!$self->_parse_value()) {
210 4         9 return;
211             }
212             }
213              
214             # set value
215 118         85 my $array = $POINTER;
216 118         111 push @$array => $value;
217              
218             # skip
219 118   100     133 1 while $self->_skip_whitespace() || $self->_skip_comments();
220              
221             # is last?
222 118 100       288 if (/\G\]/mgc) {
    100          
223 21         40 return 1;
224             }
225             elsif (/\G,/mgc) {
226 96         161 return $self->_parse_array_value;
227             }
228              
229 1         3 return;
230             }
231              
232             sub _parse_number {
233 252     252   189 my $self = shift;
234              
235 252 100       1070 if (/\G([-+])?Infinity/mgc) {
    100          
    100          
    100          
236 10   100     45 my $number = $self->{inflate_infinity}->($1 || '+');
237 10         19 ${$POINTER} = $number;
  10         11  
238 10         30 return 1;
239             }
240             elsif (/\GNaN/mgc) {
241 3         12 my $number = $self->{inflate_nan}->();
242 3         6 ${$POINTER} = $number;
  3         3  
243 3         6 return 1;
244             }
245             elsif (/\G([-+]?)0x([0-9a-f]+)/imgc) {
246 16         46 my $number = hex $2;
247 16 100 100     49 $number *= -1 if $1 && $1 eq '-';
248 16         16 ${$POINTER} = $number;
  16         18  
249 16         26 return 1;
250             }
251             elsif (/\G([-+]?(?:[0-9]+(?:\.[0-9]*)?|[0-9]*\.[0-9]+))(?:e([-+]?[0-9]+))?/mgc) {
252 93         207 my $number = 0+$1;
253 93 100       149 $number *= 10 ** $2 if defined $2;
254 93         71 ${$POINTER} = $number;
  93         96  
255 93         140 return 1;
256             }
257              
258 130         238 return;
259             }
260              
261             sub _parse_boolean {
262 130     130   101 my $self = shift;
263              
264 130 100       209 if (/\G(true|false)/mgc) {
265 12         26 my $bool = $self->{inflate_boolean}->($1);
266 12         27 ${$POINTER} = $bool;
  12         14  
267 12         27 return 1;
268             }
269              
270 118         192 return;
271             }
272              
273             sub _parse_string {
274 254     254   160 my $self = shift;
275              
276 254 100       689 if (/\G(?:"((?:.|(?<=\\)\n)*?)(?
277 97         291 my $str = join '', grep defined, $1, $2;
278              
279             # ignore escaped linefeed
280 97         121 $str =~ s!\\\n!!xmg;
281              
282             # de-escape
283 97         87 $str =~ s!\\b !\x08!xmg; # backspace (U+0008)
284 97         70 $str =~ s!\\t !\x09!xmg; # tab (U+0009)
285 97         83 $str =~ s!\\n !\x0A!xmg; # linefeed (U+000A)
286 97         81 $str =~ s!\\f !\x0C!xmg; # form feed (U+000C)
287 97         71 $str =~ s!\\r !\x0D!xmg; # carriage return (U+000D)
288 97         78 $str =~ s!\\" !\x22!xmg; # quote (U+0022)
289 97         74 $str =~ s!\\' !\x27!xmg; # single-quote (U+0027)
290 97         79 $str =~ s!\\/ !\x2F!xmg; # slash (U+002F)
291 97         76 $str =~ s!\\\\!\x5C!xmg; # backslash (U+005C)
292 97         86 $str =~ s{\\u([0-9A-Fa-f]{4})}{# unicode (U+XXXX)
293 2         10 chr hex $1
294             }xmge;
295 97         72 $str =~ s{\\U([0-9A-Fa-f]{8})}{# unicode (U+XXXXXXXX)
296 1         4 chr hex $1
297             }xmge;
298              
299 97         61 ${$POINTER} = $str;
  97         111  
300 97         151 return 1;
301             }
302              
303 157         352 return;
304             }
305              
306             sub _parse_identifier {
307 129     129   96 my $self = shift;
308              
309 129 100       299 if (/\G([a-z_\$][0-9a-z_\$]*)/imgc) {
310 127         141 my $identifier = $1;
311 127         82 ${$POINTER} = $identifier;
  127         127  
312 127         218 return 1;
313             }
314              
315 2         5 return;
316             }
317              
318             sub _error {
319 14     14   13 my ($self, $msg) = @_;
320              
321 14         11 my $src = $_;
322 14         10 my $line = 1;
323 14   50     35 my $start = pos $src || 0;
324 14   100     81 while ($src =~ /$/smgco and pos $src <= pos) {
325 27         22 $start = pos $src;
326 27         75 $line++;
327             }
328 14         12 my $end = pos $src;
329 14         14 my $len = pos() - $start;
330 14 100       20 $len-- if $len > 0;
331              
332 14   100     69 my $trace = join "\n",
333             "${msg}: line:$line",
334             substr($src, $start || 0, $end - $start),
335             (' ' x $len) . '^';
336 14         79 die $trace, "\n";
337             }
338              
339             1;
340             __END__