JSON5
view release on metacpan or search on metacpan
lib/JSON5/Parser.pm view on Meta::CPAN
package JSON5::Parser;
use strict;
use warnings;
use utf8;
use Carp qw/croak/;
use JSON::PP;
use Encode;
our $ROOT;
our $POINTER;
sub new {
my $class = shift;
return bless +{
utf8 => 0,
allow_nonref => 0,
max_size => 0,
inflate_boolean => sub { $_[0] eq 'true' ? JSON::PP::true : JSON::PP::false },
inflate_nan => sub { 0+'NaN' },
inflate_null => sub { undef },
inflate_infinity => sub { $_[0] eq '+' ? 0+'Inf' : 0+'-Inf' },
} => $class;
}
# define accessors
BEGIN {
# boolean accessors
for my $attr (qw/utf8 allow_nonref/) {
my $attr_accessor = sub {
my $self = shift;
$self->{$attr} = @_ ? shift : 1;
return $self;
};
my $attr_getter = sub {
my $self = shift;
return $self->{$attr};
};
no strict qw/refs/;
*{"$attr"} = $attr_accessor;
*{"get_$attr"} = $attr_getter;
}
# value accessors
for my $attr (qw/max_size inflate_boolean inflate_nan inflate_null inflate_infinity/) {
my $attr_accessor = sub {
my $self = shift;
$self->{$attr} = shift if @_;
return $self;
};
my $attr_getter = sub {
my $self = shift;
return $self->{$attr};
};
no strict qw/refs/;
*{"$attr"} = $attr_accessor;
*{"get_$attr"} = $attr_getter;
}
}
sub parse {
my ($self, $content) = @_;
if (my $max_size = $self->{max_size}) {
use bytes;
my $bytes = length $content;
$bytes <= $max_size
or croak sprintf 'attempted decode of JSON5 text of %s bytes size, but max_size is set to %s', $bytes, $max_size;
}
if ($self->{utf8}) {
$content = Encode::decode_utf8($content);
}
# normalize linefeed
$content =~ s!\r\n?!\n!mg;
local $ROOT;
local $POINTER = \$ROOT;
lib/JSON5/Parser.pm view on Meta::CPAN
# skip
1 while $self->_skip_whitespace() || $self->_skip_comments();
# is last?
if (/\G\}/mgc) {
return 1;
}
elsif (/\G,/mgc) {
return $self->_parse_object_kv;
}
return;
}
sub _parse_array_value {
my $self = shift;
# skip
1 while $self->_skip_whitespace() || $self->_skip_comments();
# is last?
if (/\G\]/mgc) {
return 1;
}
# parse value
my $value; {
local $POINTER = \$value;
if (!$self->_parse_value()) {
return;
}
}
# set value
my $array = $POINTER;
push @$array => $value;
# skip
1 while $self->_skip_whitespace() || $self->_skip_comments();
# is last?
if (/\G\]/mgc) {
return 1;
}
elsif (/\G,/mgc) {
return $self->_parse_array_value;
}
return;
}
sub _parse_number {
my $self = shift;
if (/\G([-+])?Infinity/mgc) {
my $number = $self->{inflate_infinity}->($1 || '+');
${$POINTER} = $number;
return 1;
}
elsif (/\GNaN/mgc) {
my $number = $self->{inflate_nan}->();
${$POINTER} = $number;
return 1;
}
elsif (/\G([-+]?)0x([0-9a-f]+)/imgc) {
my $number = hex $2;
$number *= -1 if $1 && $1 eq '-';
${$POINTER} = $number;
return 1;
}
elsif (/\G([-+]?(?:[0-9]+(?:\.[0-9]*)?|[0-9]*\.[0-9]+))(?:e([-+]?[0-9]+))?/mgc) {
my $number = 0+$1;
$number *= 10 ** $2 if defined $2;
${$POINTER} = $number;
return 1;
}
return;
}
sub _parse_boolean {
my $self = shift;
if (/\G(true|false)/mgc) {
my $bool = $self->{inflate_boolean}->($1);
${$POINTER} = $bool;
return 1;
}
return;
}
sub _parse_string {
my $self = shift;
if (/\G(?:"((?:.|(?<=\\)\n)*?)(?<!(?<!\\)\\)"|\'((?:.|(?<=\\)\n)*?)(?<!(?<!\\)\\)\')/mgc) {
my $str = join '', grep defined, $1, $2;
# ignore escaped linefeed
$str =~ s!\\\n!!xmg;
# de-escape
$str =~ s!\\b !\x08!xmg; # backspace (U+0008)
$str =~ s!\\t !\x09!xmg; # tab (U+0009)
$str =~ s!\\n !\x0A!xmg; # linefeed (U+000A)
$str =~ s!\\f !\x0C!xmg; # form feed (U+000C)
$str =~ s!\\r !\x0D!xmg; # carriage return (U+000D)
$str =~ s!\\" !\x22!xmg; # quote (U+0022)
$str =~ s!\\' !\x27!xmg; # single-quote (U+0027)
$str =~ s!\\/ !\x2F!xmg; # slash (U+002F)
$str =~ s!\\\\!\x5C!xmg; # backslash (U+005C)
$str =~ s{\\u([0-9A-Fa-f]{4})}{# unicode (U+XXXX)
chr hex $1
}xmge;
$str =~ s{\\U([0-9A-Fa-f]{8})}{# unicode (U+XXXXXXXX)
chr hex $1
}xmge;
${$POINTER} = $str;
return 1;
( run in 1.665 second using v1.01-cache-2.11-cpan-39bf76dae61 )