has
'error'
;
our
$FALSE
= Mojo::JSON::_Bool->new(0);
our
$TRUE
= Mojo::JSON::_Bool->new(1);
my
$BOM_RE
=
qr/
(?:
\357\273\277 # UTF-8
|
\377\376\0\0 # UTF-32LE
|
\0\0\376\377 # UTF-32BE
|
\376\377 # UTF-16BE
|
\377\376 # UTF-16LE
)
/
x;
my
$WHITESPACE_RE
=
qr/[\x20\x09\x0a\x0d]*/
;
my
%ESCAPE
= (
'"'
=>
'"'
,
'\\'
=>
'\\'
,
'/'
=>
'/'
,
'b'
=>
"\x07"
,
'f'
=>
"\x0C"
,
'n'
=>
"\x0A"
,
'r'
=>
"\x0D"
,
't'
=>
"\x09"
,
'u2028'
=>
"\x{2028}"
,
'u2029'
=>
"\x{2029}"
);
my
%REVERSE
;
for
(0x00 .. 0x1F, 0x7F) {
$REVERSE
{
pack
'C'
,
$_
} =
sprintf
'\u%.4X'
,
$_
}
for
my
$key
(
keys
%ESCAPE
) {
$REVERSE
{
$ESCAPE
{
$key
}} =
"\\$key"
}
my
$UTF_PATTERNS
= {
"\0\0\0[^\0]"
=>
'UTF-32BE'
,
"\0[^\0]\0[^\0]"
=>
'UTF-16BE'
,
"[^\0]\0\0\0"
=>
'UTF-32LE'
,
"[^\0]\0[^\0]\0"
=>
'UTF-16LE'
};
sub
decode {
my
(
$self
,
$string
) =
@_
;
$self
->error(
undef
);
$self
->error(
'Missing or empty input.'
) and
return
unless
$string
;
$string
=~ s/^
$BOM_RE
//go;
$self
->error(
'Wide character in input.'
) and
return
unless
utf8::downgrade(
$_
, 1);
my
$encoding
=
'UTF-8'
;
for
my
$pattern
(
keys
%$UTF_PATTERNS
) {
if
(
$string
=~ /^
$pattern
/) {
$encoding
=
$UTF_PATTERNS
->{
$pattern
};
last
;
}
}
Mojo::Util::decode
$encoding
,
$string
;
my
$res
=
eval
{
local
$_
=
$string
;
m/\G
$WHITESPACE_RE
/xogc;
my
$ref
;
if
(m/\G\[/gc) {
$ref
= _decode_array() }
elsif
(m/\G\{/gc) {
$ref
= _decode_object() }
else
{ _exception(
'Expected array or object'
) }
unless
(m/\G
$WHITESPACE_RE
\z/xogc) {
my
$got
=
ref
$ref
eq
'ARRAY'
?
'array'
:
'object'
;
_exception(
"Unexpected data after $got"
);
}
$ref
;
};
if
(!
$res
&& (
my
$e
= $@)) {
chomp
$e
;
$self
->error(
$e
);
}
return
$res
;
}
sub
encode {
my
(
$self
,
$ref
) =
@_
;
my
$string
= _encode_values(
$ref
);
Mojo::Util::encode
'UTF-8'
,
$string
;
return
$string
;
}
sub
false {
$FALSE
}
sub
true {
$TRUE
}
sub
_decode_array {
my
@array
;
until
(m/\G
$WHITESPACE_RE
\]/xogc) {
push
@array
, _decode_value();
redo
if
m/\G
$WHITESPACE_RE
,/xogc;
last
if
m/\G
$WHITESPACE_RE
\]/xogc;
_exception(
'Expected comma or right square bracket while parsing array'
);
}
return
\
@array
;
}
sub
_decode_object {
my
%hash
;
until
(m/\G
$WHITESPACE_RE
\}/xogc) {
m/\G
$WHITESPACE_RE
"/xogc
or _exception(
"Expected string while parsing object"
);
my
$key
= _decode_string();
m/\G
$WHITESPACE_RE
:/xogc
or _exception(
'Expected colon while parsing object'
);
$hash
{
$key
} = _decode_value();
redo
if
m/\G
$WHITESPACE_RE
,/xogc;
last
if
m/\G
$WHITESPACE_RE
\}/xogc;
_exception(
q/Expected comma or right curly bracket while parsing object/
);
}
return
\
%hash
;
}
sub
_decode_string {
my
$pos
=
pos
;
m/\G(((?:[^\x00-\x1F\\
"]|\\(?:["
\\\/bfnrt]|u[A-Fa-f0-9]{4})){0,32766})*)/gc;
my
$str
= $1;
unless
(m/\G"/gc) {
_exception(
'Unexpected character or invalid escape while parsing string'
)
if
m/\G[\x00-\x1F\\]/x;
_exception(
'Unterminated string'
);
}
if
(
index
(
$str
,
'\\u'
) < 0) {
$str
=~ s/\\(["\\\/bfnrt])/
$ESCAPE
{$1}/gs;
return
$str
;
}
my
$buffer
=
''
;
while
(
$str
=~ m/\G([^\\]*)\\(?:([^u])|u(.{4}))/gc) {
$buffer
.= $1;
if
($2) {
$buffer
.=
$ESCAPE
{$2} }
else
{
my
$ord
=
hex
$3;
if
((
$ord
& 0xF800) == 0xD800) {
(
$ord
& 0xFC00) == 0xD800
or
pos
(
$_
) =
$pos
+
pos
(
$str
),
_exception(
'Missing high-surrogate'
);
$str
=~ m/\G\\u([Dd][C-Fc-f]..)/gc
or
pos
(
$_
) =
$pos
+
pos
(
$str
),
_exception(
'Missing low-surrogate'
);
$ord
= 0x10000 + (
$ord
- 0xD800) * 0x400 + (
hex
($1) - 0xDC00);
}
$buffer
.=
pack
'U'
,
$ord
;
}
}
$buffer
.=
substr
$str
,
pos
(
$str
),
length
(
$str
);
return
$buffer
;
}
sub
_decode_value {
m/\G
$WHITESPACE_RE
/xogc;
return
_decode_string()
if
m/\G"/gc;
return
_decode_array()
if
m/\G\[/gc;
return
_decode_object()
if
m/\G\{/gc;
return
0 + $1
if
m/\G([-]?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)/gc;
return
$TRUE
if
m/\Gtrue/gc;
return
$FALSE
if
m/\Gfalse/gc;
return
undef
if
m/\Gnull/gc;
_exception(
'Expected string, array, object, number, boolean or null'
);
}
sub
_encode_array {
my
$array
=
shift
;
my
@array
;
for
my
$value
(
@$array
) {
push
@array
, _encode_values(
$value
);
}
my
$string
=
join
','
,
@array
;
return
"[$string]"
;
}
sub
_encode_object {
my
$object
=
shift
;
my
@values
;
for
my
$key
(
keys
%$object
) {
my
$name
= _encode_string(
$key
);
my
$value
= _encode_values(
$object
->{
$key
});
push
@values
,
"$name:$value"
;
}
my
$string
=
join
','
,
@values
;
return
"{$string}"
;
}
sub
_encode_string {
my
$string
=
shift
;
$string
=~ s/([\x00-\x1F\x7F\x{2028}\x{2029}\\\"\/\b\f\n\r\t])/
$REVERSE
{$1}/gs;
return
"\"$string\""
;
}
sub
_encode_values {
my
$value
=
shift
;
if
(
my
$ref
=
ref
$value
) {
return
_encode_array(
$value
)
if
$ref
eq
'ARRAY'
;
return
_encode_object(
$value
)
if
$ref
eq
'HASH'
;
}
return
'null'
unless
defined
$value
;
return
'false'
if
ref
$value
eq
'Mojo::JSON::_Bool'
&& !
$value
;
return
'true'
if
ref
$value
eq
'Mojo::JSON::_Bool'
&&
$value
;
my
$flags
= B::svref_2object(\
$value
)->FLAGS;
return
$value
if
$flags
& (B::SVp_IOK | B::SVp_NOK) && !(
$flags
& B::SVp_POK);
_encode_string(
$value
);
}
sub
_exception {
m/\G
$WHITESPACE_RE
/xogc;
my
$context
=
'Malformed JSON: '
.
shift
;
if
(m/\G\z/gc) {
$context
.=
' before end of data'
; }
else
{
my
@lines
=
split
/\n/,
substr
(
$_
, 0,
pos
);
$context
.=
' at line '
.
@lines
.
', offset '
.
length
(
pop
@lines
||
''
);
}
die
"$context.\n"
;
}
'0+'
=>
sub
{
$_
[0]->{value} },
'""'
=>
sub
{
$_
[0]->{value} },
fallback
=> 1;
sub
new {
shift
->SUPER::new(
value
=>
shift
) }
1;