| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#! perl |
|
2
|
|
|
|
|
|
|
|
|
3
|
11
|
|
|
11
|
|
128143
|
use v5.26; |
|
|
11
|
|
|
|
|
42
|
|
|
4
|
11
|
|
|
11
|
|
7673
|
use Object::Pad; |
|
|
11
|
|
|
|
|
172803
|
|
|
|
11
|
|
|
|
|
68
|
|
|
5
|
11
|
|
|
11
|
|
2732
|
use utf8; |
|
|
11
|
|
|
|
|
444
|
|
|
|
11
|
|
|
|
|
111
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
package JSON::Relaxed::Parser; |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = "0.098"; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
class JSON::Relaxed::Parser; |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Instance data. |
|
14
|
6
|
|
|
6
|
0
|
15
|
field $data :mutator; # RJSON string being parser |
|
|
6
|
|
|
|
|
41
|
|
|
15
|
|
|
|
|
|
|
field @pretoks; # string in pre-tokens |
|
16
|
|
|
|
|
|
|
field @tokens; # string as tokens |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# Instance properties. |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Enforce strictness to official standard. |
|
21
|
|
|
|
|
|
|
# Strict true -> RJSON conformant. |
|
22
|
|
|
|
|
|
|
# Strict false (default) -> RRJSON. Everything goes :). |
|
23
|
3
|
|
|
3
|
0
|
20
|
field $strict :mutator :param = 0; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# Allow extra stuff after the JSON structure. |
|
26
|
|
|
|
|
|
|
# Strict mode only. |
|
27
|
3
|
|
|
0
|
0
|
10
|
field $extra_tokens_ok :mutator :param = 0; |
|
|
0
|
|
|
|
|
0
|
|
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# Define the values to be used for true and false. |
|
30
|
0
|
|
|
20
|
0
|
0
|
field $booleans :mutator :param = 1; |
|
|
20
|
|
|
|
|
104
|
|
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# Signal error with exceptions. |
|
33
|
20
|
|
|
1
|
0
|
103
|
field $croak_on_error :mutator :param = 1; |
|
|
1
|
|
|
|
|
9
|
|
|
34
|
1
|
|
|
|
|
3
|
field $croak_on_error_internal; |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# Some non-strict extensions can be controlled individually. |
|
37
|
|
|
|
|
|
|
# This may disappear in some futer version, so do not use. |
|
38
|
|
|
|
|
|
|
# Extension: a.b:c -> a:{b:c} |
|
39
|
|
|
|
|
|
|
## Non-strict only. |
|
40
|
0
|
|
|
0
|
0
|
0
|
field $combined_keys :mutator :param = 1; |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# Extension: a:b -> {a:b} (if outer) |
|
43
|
|
|
|
|
|
|
## Non-strict only. |
|
44
|
0
|
|
|
0
|
0
|
0
|
field $implied_outer_hash :mutator :param = 1; |
|
|
0
|
|
|
|
|
0
|
|
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Extension: = as :, and optional before {, off/on as false/true |
|
47
|
|
|
|
|
|
|
## Non-strict only. |
|
48
|
0
|
|
|
99
|
0
|
0
|
field $prp :mutator :param = 1; |
|
|
99
|
|
|
|
|
199
|
|
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# Formatted output. |
|
51
|
99
|
|
|
0
|
0
|
534
|
field $pretty :mutator :param = 0; |
|
|
0
|
|
|
|
|
0
|
|
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Retain key order. Warning: adds a key " key order " to each hash! |
|
54
|
|
|
|
|
|
|
## Non-strict only. |
|
55
|
0
|
|
|
0
|
0
|
0
|
field $key_order :mutator :param = 0; |
|
|
0
|
|
|
|
|
0
|
|
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Error indicators. |
|
58
|
0
|
50
|
|
2
|
0
|
0
|
field $err_id :accessor; |
|
|
2
|
|
|
|
|
8
|
|
|
|
2
|
|
|
|
|
14
|
|
|
59
|
2
|
50
|
|
2
|
0
|
1045
|
field $err_msg :accessor; |
|
|
2
|
|
|
|
|
28
|
|
|
60
|
0
|
0
|
|
0
|
0
|
0
|
field $err_pos :accessor; |
|
|
0
|
|
|
|
|
0
|
|
|
61
|
|
|
|
|
|
|
|
|
62
|
30
|
|
|
30
|
0
|
50588
|
method decode( $str ) { |
|
|
30
|
|
|
|
|
143
|
|
|
|
30
|
|
|
|
|
53
|
|
|
|
30
|
|
|
|
|
48
|
|
|
63
|
30
|
|
|
|
|
47
|
$croak_on_error_internal = $croak_on_error; |
|
64
|
30
|
|
|
|
|
87
|
$self->_decode($str); |
|
65
|
|
|
|
|
|
|
} |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# Legacy. |
|
68
|
65
|
|
|
65
|
0
|
3584
|
method parse( $str ) { |
|
|
65
|
|
|
|
|
250
|
|
|
|
65
|
|
|
|
|
323
|
|
|
|
65
|
|
|
|
|
146
|
|
|
69
|
65
|
|
|
|
|
143
|
$croak_on_error_internal = 0; |
|
70
|
65
|
|
|
|
|
391
|
$self->_decode($str); |
|
71
|
|
|
|
|
|
|
} |
|
72
|
|
|
|
|
|
|
|
|
73
|
95
|
|
|
95
|
|
175
|
method _decode( $str ) { |
|
|
95
|
|
|
|
|
258
|
|
|
|
95
|
|
|
|
|
153
|
|
|
|
95
|
|
|
|
|
143
|
|
|
74
|
|
|
|
|
|
|
|
|
75
|
95
|
|
|
|
|
181
|
$data = $str; |
|
76
|
95
|
50
|
33
|
|
|
585
|
return $self->error('missing-input') |
|
77
|
|
|
|
|
|
|
unless defined $data && length $data; |
|
78
|
|
|
|
|
|
|
|
|
79
|
95
|
|
|
|
|
177
|
undef $err_id; |
|
80
|
95
|
|
|
|
|
150
|
$err_pos = -1; |
|
81
|
95
|
|
|
|
|
165
|
undef $err_msg; |
|
82
|
|
|
|
|
|
|
|
|
83
|
95
|
|
|
|
|
418
|
$self->pretokenize; |
|
84
|
95
|
50
|
|
|
|
351
|
return if $self->is_error; |
|
85
|
|
|
|
|
|
|
|
|
86
|
95
|
|
|
|
|
416
|
$self->tokenize; |
|
87
|
95
|
50
|
|
|
|
216
|
return $self->error('empty-input') unless @tokens; |
|
88
|
|
|
|
|
|
|
|
|
89
|
95
|
|
|
|
|
291
|
$self->structure( top => 1 ); |
|
90
|
|
|
|
|
|
|
} |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
################ Character classifiers ################ |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# Reserved characters. |
|
95
|
|
|
|
|
|
|
# '[' beginning of array |
|
96
|
|
|
|
|
|
|
# ']' end of array |
|
97
|
|
|
|
|
|
|
# '{' beginning of hash |
|
98
|
|
|
|
|
|
|
# '}' end of hash |
|
99
|
|
|
|
|
|
|
# ':' delimiter between name and value of hash element |
|
100
|
|
|
|
|
|
|
# ',' separator between elements in hashes and arrays |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
my $p_reserved = q<[,:{}\[\]]>; |
|
103
|
|
|
|
|
|
|
|
|
104
|
621
|
|
|
621
|
0
|
854
|
method is_reserved ($c) { |
|
|
621
|
|
|
|
|
1193
|
|
|
|
621
|
|
|
|
|
812
|
|
|
|
621
|
|
|
|
|
755
|
|
|
105
|
621
|
|
|
|
|
5744
|
$c =~ /^$p_reserved$/; |
|
106
|
|
|
|
|
|
|
} |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
# Newlines. CRLF (Windows), CR (MacOS) and newline (sane systems). |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
my $p_newlines = q{(?:\r\n|\r|\n|\\\n)}; |
|
111
|
|
|
|
|
|
|
|
|
112
|
0
|
|
|
0
|
0
|
0
|
method is_newline ($c) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
113
|
0
|
|
|
|
|
0
|
$c =~ /^$p_newlines$/o; |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Quotes. Single, double and backtick. |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
my $p_quotes = q{["'`]}; |
|
119
|
|
|
|
|
|
|
|
|
120
|
178
|
|
|
178
|
0
|
322
|
method is_quote ($c) { |
|
|
178
|
|
|
|
|
338
|
|
|
|
178
|
|
|
|
|
266
|
|
|
|
178
|
|
|
|
|
249
|
|
|
121
|
178
|
|
|
|
|
824
|
$c =~ /^$p_quotes$/o; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# Numbers. A special case of unquoted strings. |
|
125
|
|
|
|
|
|
|
my $p_number = q{[+-]?\d*\.?\d+(?:[Ee][+-]?\d+)?}; |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
method pretokenize { |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
# \u escape (4 hexits) |
|
130
|
|
|
|
|
|
|
my @p = ( qq<\\\\u[[:xdigit:]]{4}> ); |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
# Any escaped char (strict mode). |
|
133
|
|
|
|
|
|
|
if ( $strict ) { |
|
134
|
|
|
|
|
|
|
push( @p, qq<\\\\.> ); |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Otherwise, match \u{ ... } also. |
|
138
|
|
|
|
|
|
|
else { |
|
139
|
|
|
|
|
|
|
push( @p, qq<\\\\u\\{[[:xdigit:]]+\\}>, qq<\\\\[^u]> ); # escaped char |
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
if ( $prp && !$strict ) { |
|
143
|
|
|
|
|
|
|
# Add = to the reserved characters |
|
144
|
|
|
|
|
|
|
$p_reserved = q<[,=:{}\[\]]>; |
|
145
|
|
|
|
|
|
|
# Massage # comments into // comments without affecting position. |
|
146
|
|
|
|
|
|
|
$data =~ s/^(\s*)#.(.*)$/$1\/\/$2/gm; |
|
147
|
|
|
|
|
|
|
$data =~ s/^(\s*)#$/$1 /gm; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
push( @p, $p_newlines, |
|
151
|
|
|
|
|
|
|
qq< // [^\\n]* \\n >, # line comment |
|
152
|
|
|
|
|
|
|
qq< /\\* .*? \\*/ >, # comment start |
|
153
|
|
|
|
|
|
|
qq< /\\* >, # comment start |
|
154
|
|
|
|
|
|
|
qq< $p_reserved >, # reserved chars |
|
155
|
|
|
|
|
|
|
qq< "(?:\\\\.|.)*?" >, # "string" |
|
156
|
|
|
|
|
|
|
qq< `(?:\\\\.|.)*?` >, # `string` |
|
157
|
|
|
|
|
|
|
qq< '(?:\\\\.|.)*?' >, # 'string' |
|
158
|
|
|
|
|
|
|
qq< $p_quotes >, # stringquote |
|
159
|
|
|
|
|
|
|
qq< \\s+ > ); # whitespace |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
my $p = join( "|", @p ); |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
@pretoks = split( m< ( $p ) >sox, $data ); |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Remove empty strings. |
|
166
|
|
|
|
|
|
|
@pretoks = grep { length($_) } @pretoks; |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
return; |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# Accessor for @pretoks. |
|
172
|
0
|
|
|
0
|
0
|
0
|
method pretoks() { \@pretoks } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
method tokenize { |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
@tokens = (); |
|
177
|
|
|
|
|
|
|
my $offset = 0; # token offset in input |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
if ( $booleans ) { |
|
180
|
|
|
|
|
|
|
if ( ref($booleans) ne 'ARRAY' ) { |
|
181
|
|
|
|
|
|
|
$booleans = [ $JSON::Boolean::false, $JSON::Boolean::true ]; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
else { |
|
185
|
|
|
|
|
|
|
$booleans = [ 0, 1 ]; |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
my $glue = 0; # can glue strings |
|
189
|
|
|
|
|
|
|
my $uq_open = 0; # collecting pretokens for unquoted string |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Loop through characters. |
|
192
|
|
|
|
|
|
|
while ( @pretoks ) { |
|
193
|
|
|
|
|
|
|
my $pretok = shift(@pretoks); |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# White space: ignore. |
|
196
|
|
|
|
|
|
|
if ( $pretok !~ /\S/ ) { |
|
197
|
|
|
|
|
|
|
$offset += length($pretok); |
|
198
|
|
|
|
|
|
|
$uq_open = 0; |
|
199
|
|
|
|
|
|
|
next; |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
if ( $pretok eq "\\\n" ) { |
|
203
|
|
|
|
|
|
|
$glue++ if $glue; |
|
204
|
|
|
|
|
|
|
$uq_open = 0; |
|
205
|
|
|
|
|
|
|
$offset += length($pretok); |
|
206
|
|
|
|
|
|
|
next; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# Strings. |
|
210
|
|
|
|
|
|
|
if ( $pretok =~ /^(["'`])(.*?)\1$/s ) { |
|
211
|
|
|
|
|
|
|
my ( $quote, $content ) = ( $1, $2 ); |
|
212
|
|
|
|
|
|
|
if ( $glue > 1 ) { |
|
213
|
|
|
|
|
|
|
$tokens[-1]->append($content); |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
else { |
|
216
|
|
|
|
|
|
|
$self->addtok( $content, 'Q', $offset, $quote ); |
|
217
|
|
|
|
|
|
|
$glue = 1 unless $strict; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
|
|
|
|
|
|
$offset += length($pretok); |
|
220
|
|
|
|
|
|
|
$uq_open = 0; |
|
221
|
|
|
|
|
|
|
next; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
$glue = 0; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# // comment. |
|
226
|
|
|
|
|
|
|
if ( $pretok =~ m<^//(.*)> ) { |
|
227
|
|
|
|
|
|
|
# $self->addtok( $1, 'L', $offset ); |
|
228
|
|
|
|
|
|
|
$offset += length($pretok); |
|
229
|
|
|
|
|
|
|
$uq_open = 0; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# /* comment */ |
|
233
|
|
|
|
|
|
|
elsif ( $pretok =~ m<^/\*.+>s ) { |
|
234
|
|
|
|
|
|
|
$offset += length($pretok); |
|
235
|
|
|
|
|
|
|
$uq_open = 0; |
|
236
|
|
|
|
|
|
|
} |
|
237
|
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
elsif ( $pretok eq '/*' ) { |
|
239
|
|
|
|
|
|
|
return $self->error('unclosed-inline-comment'); |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
# Reserved characters. |
|
243
|
|
|
|
|
|
|
elsif ( $self->is_reserved($pretok) ) { |
|
244
|
|
|
|
|
|
|
$self->addtok( $pretok, 'C', $offset ); |
|
245
|
|
|
|
|
|
|
$offset += length($pretok); |
|
246
|
|
|
|
|
|
|
$uq_open = 0; |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Numbers. |
|
251
|
|
|
|
|
|
|
elsif ( $pretok =~ /^$p_number$/ ) { |
|
252
|
|
|
|
|
|
|
$self->addtok( 0+$pretok, 'N', $offset ); |
|
253
|
|
|
|
|
|
|
$offset += length($pretok); |
|
254
|
|
|
|
|
|
|
$uq_open = 0; |
|
255
|
|
|
|
|
|
|
} |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# Quotes |
|
258
|
|
|
|
|
|
|
# Can't happen -- should be an encosed string. |
|
259
|
|
|
|
|
|
|
elsif ( $self->is_quote($pretok) ) { |
|
260
|
|
|
|
|
|
|
$offset += length($pretok); |
|
261
|
|
|
|
|
|
|
$self->addtok( $pretok, '?', $offset ); |
|
262
|
|
|
|
|
|
|
return $self->error('unclosed-quote', $tokens[-1] ); |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# Else it's an unquoted string. |
|
266
|
|
|
|
|
|
|
else { |
|
267
|
|
|
|
|
|
|
if ( $uq_open ) { |
|
268
|
|
|
|
|
|
|
$tokens[-1]->append($pretok); |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
else { |
|
271
|
|
|
|
|
|
|
$self->addtok( $pretok, 'U', $offset ); |
|
272
|
|
|
|
|
|
|
$uq_open++; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
$offset += length($pretok); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
return; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# Accessor for @tokens, |
|
281
|
0
|
|
|
0
|
0
|
0
|
method tokens() { \@tokens } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Add a new token to @tokens. |
|
284
|
807
|
|
|
807
|
0
|
957
|
method addtok( $tok, $typ, $off, $quote=undef ) { |
|
|
807
|
|
|
|
|
1466
|
|
|
|
807
|
|
|
|
|
1058
|
|
|
|
807
|
|
|
|
|
1034
|
|
|
|
807
|
|
|
|
|
1078
|
|
|
|
807
|
|
|
|
|
1175
|
|
|
|
807
|
|
|
|
|
939
|
|
|
285
|
|
|
|
|
|
|
|
|
286
|
807
|
100
|
100
|
|
|
8378
|
push( @tokens, |
|
|
|
100
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
$typ eq 'U' || $typ eq 'N' |
|
288
|
|
|
|
|
|
|
? JSON::Relaxed::String::Unquoted->new( token => $tok, |
|
289
|
|
|
|
|
|
|
content => $tok, |
|
290
|
|
|
|
|
|
|
type => $typ, |
|
291
|
|
|
|
|
|
|
parent => $self, |
|
292
|
|
|
|
|
|
|
offset => $off ) |
|
293
|
|
|
|
|
|
|
: $typ eq 'Q' |
|
294
|
|
|
|
|
|
|
? JSON::Relaxed::String::Quoted->new( token => $tok, |
|
295
|
|
|
|
|
|
|
type => $typ, |
|
296
|
|
|
|
|
|
|
content => $tok, |
|
297
|
|
|
|
|
|
|
quote => $quote, |
|
298
|
|
|
|
|
|
|
parent => $self, |
|
299
|
|
|
|
|
|
|
offset => $off ) |
|
300
|
|
|
|
|
|
|
: JSON::Relaxed::Token->new( token => $tok, |
|
301
|
|
|
|
|
|
|
parent => $self, |
|
302
|
|
|
|
|
|
|
type => $typ, |
|
303
|
|
|
|
|
|
|
offset => $off ) ); |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Build the result structure out of the tokens. |
|
307
|
170
|
|
|
170
|
0
|
256
|
method structure( %opts ) { |
|
|
170
|
|
|
|
|
380
|
|
|
|
170
|
|
|
|
|
316
|
|
|
|
170
|
|
|
|
|
203
|
|
|
308
|
|
|
|
|
|
|
|
|
309
|
170
|
50
|
|
|
|
439
|
@tokens = @{$opts{tokens}} if $opts{tokens}; # for debugging |
|
|
0
|
|
|
|
|
0
|
|
|
310
|
|
|
|
|
|
|
|
|
311
|
170
|
100
|
100
|
|
|
610
|
if ( $implied_outer_hash && !$strict ) { |
|
312
|
|
|
|
|
|
|
# Note that = can only occur with $prp. |
|
313
|
163
|
100
|
100
|
|
|
570
|
if ( @tokens > 2 && $tokens[0]->is_string |
|
|
|
|
66
|
|
|
|
|
|
314
|
|
|
|
|
|
|
&& $tokens[1]->token =~ /[:={]/ ) { |
|
315
|
5
|
|
|
|
|
38
|
$self->addtok( '}', 'C', $tokens[-1]->offset ); |
|
316
|
5
|
|
|
|
|
35
|
$self->addtok( '{', 'C', $tokens[0]->offset ); |
|
317
|
5
|
|
|
|
|
14
|
unshift( @tokens, pop(@tokens )); |
|
318
|
|
|
|
|
|
|
} |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
|
|
321
|
170
|
|
50
|
|
|
407
|
my $this = shift(@tokens) // return; |
|
322
|
170
|
|
|
|
|
270
|
my $rv; |
|
323
|
|
|
|
|
|
|
|
|
324
|
170
|
100
|
|
|
|
432
|
if ( $this->is_string ) { # (un)quoted string |
|
325
|
55
|
|
|
|
|
157
|
$rv = $this->as_perl; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
else { |
|
328
|
115
|
|
|
|
|
200
|
my $t = $this->token; |
|
329
|
115
|
100
|
|
|
|
265
|
if ( $t eq '{' ) { |
|
|
|
50
|
|
|
|
|
|
|
330
|
56
|
|
|
|
|
178
|
$rv = $self->build_hash; |
|
331
|
|
|
|
|
|
|
} |
|
332
|
|
|
|
|
|
|
elsif ( $t eq '[' ) { |
|
333
|
59
|
|
|
|
|
156
|
$rv = $self->build_array; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
else { |
|
336
|
0
|
|
|
|
|
0
|
return $self->error( 'invalid-structure-opening-character', |
|
337
|
|
|
|
|
|
|
$this ); |
|
338
|
|
|
|
|
|
|
} |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# If this is the outer structure, then no tokens should remain. |
|
342
|
170
|
100
|
100
|
|
|
714
|
if ( $opts{top} |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
343
|
|
|
|
|
|
|
&& @tokens |
|
344
|
|
|
|
|
|
|
&& ( $strict || !$extra_tokens_ok ) |
|
345
|
|
|
|
|
|
|
&& !$self->is_error |
|
346
|
|
|
|
|
|
|
) { |
|
347
|
3
|
|
|
|
|
11
|
return $self->error( 'multiple-structures', $tokens[0] ); |
|
348
|
|
|
|
|
|
|
} |
|
349
|
|
|
|
|
|
|
|
|
350
|
167
|
|
|
|
|
1003
|
return $rv; |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
|
|
354
|
4
|
|
|
4
|
0
|
29
|
method error( $id, $aux = undef ) { |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
7
|
|
|
355
|
4
|
|
|
|
|
2315
|
require JSON::Relaxed::ErrorCodes; |
|
356
|
4
|
|
|
|
|
14
|
$err_id = $id; |
|
357
|
4
|
50
|
|
|
|
42
|
$err_pos = $aux ? $aux->offset : -1; |
|
358
|
4
|
|
|
|
|
17
|
$err_msg = JSON::Relaxed::ErrorCodes->message( $id, $aux ); |
|
359
|
|
|
|
|
|
|
|
|
360
|
4
|
50
|
|
|
|
12
|
die( $err_msg, "\n" ) if $croak_on_error_internal; |
|
361
|
4
|
|
|
|
|
35
|
return; # undef |
|
362
|
|
|
|
|
|
|
} |
|
363
|
|
|
|
|
|
|
|
|
364
|
198
|
|
|
198
|
0
|
23333
|
method is_error() { |
|
|
198
|
|
|
|
|
640
|
|
|
|
198
|
|
|
|
|
280
|
|
|
365
|
198
|
|
|
|
|
598
|
$err_id; |
|
366
|
|
|
|
|
|
|
} |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
# For debugging. |
|
369
|
0
|
|
|
0
|
0
|
0
|
method dump_tokens() { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
370
|
0
|
|
|
|
|
0
|
my $tokens = \@tokens; |
|
371
|
0
|
0
|
|
|
|
0
|
return unless require DDP; |
|
372
|
0
|
0
|
|
|
|
0
|
if ( -t STDERR ) { |
|
373
|
0
|
|
|
|
|
0
|
DDP::p($tokens); |
|
374
|
|
|
|
|
|
|
} |
|
375
|
|
|
|
|
|
|
else { |
|
376
|
0
|
|
|
|
|
0
|
warn DDP::np($tokens), "\n"; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
|
|
380
|
56
|
|
|
56
|
0
|
70
|
method build_hash() { |
|
|
56
|
|
|
|
|
100
|
|
|
|
56
|
|
|
|
|
67
|
|
|
381
|
|
|
|
|
|
|
|
|
382
|
56
|
|
|
|
|
87
|
my $rv = {}; |
|
383
|
56
|
|
|
|
|
93
|
my @ko; # order of keys |
|
384
|
|
|
|
|
|
|
|
|
385
|
56
|
|
|
|
|
125
|
while ( @tokens ) { |
|
386
|
147
|
|
|
|
|
204
|
my $this = shift(@tokens); |
|
387
|
|
|
|
|
|
|
# What is allowed after opening brace: |
|
388
|
|
|
|
|
|
|
# closing brace |
|
389
|
|
|
|
|
|
|
# comma |
|
390
|
|
|
|
|
|
|
# string |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
# If closing brace, return. |
|
393
|
147
|
|
|
|
|
327
|
my $t = $this->token; |
|
394
|
147
|
100
|
|
|
|
313
|
if ( $t eq '}' ) { |
|
395
|
55
|
100
|
66
|
|
|
139
|
$rv->{" key order "} = \@ko |
|
|
|
|
100
|
|
|
|
|
|
396
|
|
|
|
|
|
|
if $key_order && !$strict && @ko > 1; |
|
397
|
55
|
|
|
|
|
151
|
return $rv; |
|
398
|
|
|
|
|
|
|
} |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
# If comma, do nothing. |
|
401
|
92
|
100
|
|
|
|
211
|
next if $t eq ','; |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# String |
|
404
|
|
|
|
|
|
|
# If the token is a string then it is a key. The token after that |
|
405
|
|
|
|
|
|
|
# should be a value. |
|
406
|
79
|
50
|
|
|
|
176
|
if ( $this->is_string ) { |
|
407
|
79
|
|
|
|
|
126
|
my ( $key, $value ); |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
# Set key using string. |
|
410
|
79
|
|
|
|
|
174
|
$key = $this->as_perl( always_string => 1 ); |
|
411
|
79
|
|
|
|
|
232
|
$self->set_value( $rv, $key ); |
|
412
|
79
|
100
|
|
|
|
160
|
if ( $key_order ) { |
|
413
|
6
|
50
|
33
|
|
|
24
|
if ( $combined_keys && !$strict ) { |
|
414
|
6
|
|
|
|
|
33
|
push( @ko, $key =~ s/\..*//r ); |
|
415
|
|
|
|
|
|
|
} |
|
416
|
|
|
|
|
|
|
else { |
|
417
|
0
|
|
|
|
|
0
|
push( @ko, $key ); |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
|
|
421
|
79
|
|
|
|
|
119
|
my $next = $tokens[0]; |
|
422
|
|
|
|
|
|
|
# If anything follows the string. |
|
423
|
79
|
50
|
|
|
|
144
|
last unless defined $next; |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# A comma or closing brace is acceptable after a string. |
|
426
|
79
|
50
|
33
|
|
|
165
|
next if $next->token eq ',' || $next->token eq '}'; |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# If next token is a colon or equals then it should be followed by a value. |
|
429
|
|
|
|
|
|
|
# Note that = can only occur with $prp. |
|
430
|
79
|
100
|
33
|
|
|
150
|
if ( $next->token =~ /^[:=]$/ ) { |
|
|
|
50
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
# Step past the colon. |
|
432
|
75
|
|
|
|
|
107
|
shift(@tokens); |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
# If at end of token array, exit loop. |
|
435
|
75
|
50
|
|
|
|
148
|
last unless @tokens; |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
# Get hash value. |
|
438
|
75
|
|
|
|
|
167
|
$value = $self->get_value; |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# If there is a global error, return undef. |
|
441
|
75
|
100
|
|
|
|
209
|
return undef if $self->is_error; |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# Extension (prp): Implied colon. |
|
445
|
|
|
|
|
|
|
elsif ( $prp && $next->token eq '{' ) { |
|
446
|
|
|
|
|
|
|
# Get hash value. |
|
447
|
4
|
|
|
|
|
15
|
$value = $self->get_value; |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# If there is a global error, return undef. |
|
450
|
4
|
50
|
|
|
|
11
|
return undef if $self->is_error; |
|
451
|
|
|
|
|
|
|
} |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# Anything else is an error. |
|
454
|
|
|
|
|
|
|
else { |
|
455
|
0
|
|
|
|
|
0
|
return $self->error('unknown-token-after-key', $next ); |
|
456
|
|
|
|
|
|
|
} |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
# Set key and value in return hash. |
|
459
|
78
|
|
|
|
|
265
|
$self->set_value( $rv, $key, $value ); |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
# Anything else is an error. |
|
463
|
|
|
|
|
|
|
else { |
|
464
|
0
|
|
|
|
|
0
|
return $self->error('unknown-token-for-hash-key', $this ); |
|
465
|
|
|
|
|
|
|
} |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
# If we get this far then unclosed brace. |
|
469
|
0
|
|
|
|
|
0
|
return $self->error('unclosed-hash-brace'); |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
|
|
473
|
79
|
|
|
79
|
0
|
139
|
method get_value() { |
|
|
79
|
|
|
|
|
166
|
|
|
|
79
|
|
|
|
|
99
|
|
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# Get token. |
|
476
|
79
|
|
|
|
|
126
|
my $this = shift(@tokens); |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
# Token must be string, array, or hash. |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
# String. |
|
481
|
79
|
100
|
|
|
|
169
|
if ( $this->is_string ) { |
|
|
|
50
|
|
|
|
|
|
|
482
|
56
|
|
|
|
|
112
|
return $this->as_perl; |
|
483
|
|
|
|
|
|
|
} |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# Token opens a hash or array. |
|
486
|
|
|
|
|
|
|
elsif ( $this->is_list_opener ) { |
|
487
|
23
|
|
|
|
|
52
|
unshift( @tokens, $this ); |
|
488
|
23
|
|
|
|
|
163
|
return $self->structure; |
|
489
|
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
# At this point it's an illegal token. |
|
492
|
0
|
|
|
|
|
0
|
return $self->error('unexpected-token-after-colon', $this ); |
|
493
|
|
|
|
|
|
|
} |
|
494
|
|
|
|
|
|
|
|
|
495
|
157
|
|
|
157
|
0
|
257
|
method set_value ( $rv, $key, $value = undef ) { |
|
|
157
|
|
|
|
|
271
|
|
|
|
157
|
|
|
|
|
206
|
|
|
|
157
|
|
|
|
|
231
|
|
|
|
157
|
|
|
|
|
246
|
|
|
|
157
|
|
|
|
|
219
|
|
|
496
|
157
|
100
|
100
|
|
|
1014
|
return $rv->{$key} = $value |
|
|
|
|
100
|
|
|
|
|
|
497
|
|
|
|
|
|
|
unless $combined_keys && !$strict && $key =~ /\./s; |
|
498
|
|
|
|
|
|
|
|
|
499
|
18
|
|
|
|
|
58
|
my @keys = split(/\./, $key, -1 ); |
|
500
|
18
|
|
|
|
|
51
|
my $c = \$rv; |
|
501
|
18
|
|
|
|
|
43
|
for ( @keys ) { |
|
502
|
36
|
50
|
|
|
|
144
|
if ( /^[+-]?\d+$/ ) { |
|
503
|
0
|
|
|
|
|
0
|
$c = \( $$c->[$_] ); |
|
504
|
|
|
|
|
|
|
} |
|
505
|
|
|
|
|
|
|
else { |
|
506
|
36
|
|
|
|
|
102
|
$c = \( $$c->{$_} ); |
|
507
|
|
|
|
|
|
|
} |
|
508
|
|
|
|
|
|
|
} |
|
509
|
18
|
|
|
|
|
110
|
$$c = $value; |
|
510
|
|
|
|
|
|
|
} |
|
511
|
|
|
|
|
|
|
|
|
512
|
59
|
|
|
59
|
0
|
75
|
method build_array() { |
|
|
59
|
|
|
|
|
95
|
|
|
|
59
|
|
|
|
|
72
|
|
|
513
|
|
|
|
|
|
|
|
|
514
|
59
|
|
|
|
|
79
|
my $rv = []; |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
# Build array. Work through tokens until closing brace. |
|
517
|
59
|
|
|
|
|
119
|
while ( @tokens ) { |
|
518
|
357
|
|
|
|
|
562
|
my $this = shift(@tokens); |
|
519
|
|
|
|
|
|
|
|
|
520
|
357
|
|
|
|
|
606
|
my $t = $this->token; |
|
521
|
|
|
|
|
|
|
# Closing brace: we're done building this array. |
|
522
|
357
|
100
|
|
|
|
845
|
return $rv if $t eq ']'; |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# Comma: if we get to a comma at this point, and we have |
|
525
|
|
|
|
|
|
|
# content, do nothing with it in strict mode. Ignore otherwise. |
|
526
|
299
|
100
|
100
|
|
|
996
|
if ( $t eq ',' && (!$strict || @$rv) ) { |
|
|
|
100
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
} |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
# Opening brace of hash or array. |
|
530
|
|
|
|
|
|
|
elsif ( $this->is_list_opener ) { |
|
531
|
52
|
|
|
|
|
76
|
unshift( @tokens, $this ); |
|
532
|
52
|
|
|
|
|
96
|
my $object = $self->structure; |
|
533
|
52
|
50
|
|
|
|
83
|
defined($object) or return undef; |
|
534
|
52
|
|
|
|
|
123
|
push( @$rv, $object ); |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# if string, add it to the array |
|
538
|
|
|
|
|
|
|
elsif ( $this->is_string ) { |
|
539
|
|
|
|
|
|
|
# add the string to the array |
|
540
|
158
|
|
|
|
|
330
|
push( @$rv, $this->as_perl ); |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# Check following token. |
|
543
|
158
|
50
|
|
|
|
293
|
if ( @tokens ) { |
|
544
|
158
|
|
50
|
|
|
371
|
my $next = $tokens[0] || ''; |
|
545
|
|
|
|
|
|
|
# Spec say: Commas are optional between objects pairs |
|
546
|
|
|
|
|
|
|
# and array items. |
|
547
|
|
|
|
|
|
|
# The next element must be a comma or the closing brace, |
|
548
|
|
|
|
|
|
|
# or a string or list. |
|
549
|
|
|
|
|
|
|
# Anything else is an error. |
|
550
|
158
|
50
|
66
|
|
|
268
|
unless ( $next->token =~ /^[,\]]$/ |
|
|
|
|
33
|
|
|
|
|
|
551
|
|
|
|
|
|
|
|| $next->is_string |
|
552
|
|
|
|
|
|
|
|| $next->is_list_opener ) { |
|
553
|
0
|
|
|
|
|
0
|
return $self->error( 'missing_comma-between-array-elements', |
|
554
|
|
|
|
|
|
|
$next ); |
|
555
|
|
|
|
|
|
|
} |
|
556
|
|
|
|
|
|
|
} |
|
557
|
|
|
|
|
|
|
} |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
# Else unkown object or character, so throw error. |
|
560
|
|
|
|
|
|
|
else { |
|
561
|
1
|
|
|
|
|
7
|
return $self->error( 'unknown-array-token', $this ); |
|
562
|
|
|
|
|
|
|
} |
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
# If we get this far then unclosed brace. |
|
566
|
0
|
|
|
|
|
0
|
return $self->error('unclosed-array-brace'); |
|
567
|
|
|
|
|
|
|
} |
|
568
|
|
|
|
|
|
|
|
|
569
|
0
|
|
|
0
|
0
|
0
|
method is_comment_opener( $pretok ) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
570
|
0
|
0
|
|
|
|
0
|
$pretok eq '//' || $pretok eq '/*'; |
|
571
|
|
|
|
|
|
|
} |
|
572
|
|
|
|
|
|
|
|
|
573
|
11
|
|
|
11
|
|
147778
|
use List::Util qw( min max uniqstr ); |
|
|
11
|
|
|
|
|
32
|
|
|
|
11
|
|
|
|
|
96617
|
|
|
574
|
|
|
|
|
|
|
|
|
575
|
17
|
|
|
17
|
0
|
5293
|
method encode(%opts) { |
|
|
17
|
|
|
|
|
78
|
|
|
|
17
|
|
|
|
|
76
|
|
|
|
17
|
|
|
|
|
24
|
|
|
576
|
17
|
|
|
|
|
62
|
my $schema = $opts{schema}; |
|
577
|
17
|
|
100
|
|
|
61
|
my $level = $opts{level} // 0; |
|
578
|
17
|
|
|
|
|
33
|
my $rv = $opts{data}; # allow undef |
|
579
|
17
|
|
50
|
|
|
62
|
my $indent = $opts{indent} // 2; |
|
580
|
17
|
|
33
|
|
|
56
|
my $impoh = $opts{implied_outer_hash} // $implied_outer_hash; |
|
581
|
17
|
|
33
|
|
|
57
|
my $ckeys = $opts{combined_keys} // $combined_keys; |
|
582
|
17
|
|
33
|
|
|
60
|
my $prpmode = $opts{prp} // $prp; |
|
583
|
17
|
|
66
|
|
|
65
|
my $pretty = $opts{pretty} // $pretty; |
|
584
|
17
|
|
33
|
|
|
50
|
my $strict = $opts{strict} // $strict; |
|
585
|
17
|
|
50
|
|
|
58
|
my $nouesc = $opts{nounicodeescapes} // 0; |
|
586
|
|
|
|
|
|
|
|
|
587
|
17
|
50
|
|
|
|
61
|
if ( $strict ) { |
|
588
|
0
|
|
|
|
|
0
|
$ckeys = $prpmode = $impoh = 0; |
|
589
|
|
|
|
|
|
|
} |
|
590
|
|
|
|
|
|
|
|
|
591
|
17
|
50
|
|
|
|
39
|
$schema = resolve( $schema, $schema ) if $schema; |
|
592
|
|
|
|
|
|
|
|
|
593
|
17
|
|
|
|
|
33
|
my $s = ""; |
|
594
|
17
|
|
|
|
|
26
|
my $i = 0; |
|
595
|
17
|
|
|
|
|
35
|
my $props = $schema->{properties}; |
|
596
|
|
|
|
|
|
|
#warn("L$level - ", join(" ", sort keys(%$props)),"\n"); |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
# Add comments from schema, if any. |
|
599
|
5
|
|
|
5
|
|
11
|
my $comments = sub( $p ) { |
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
10
|
|
|
600
|
5
|
|
|
|
|
11
|
my $s = ""; |
|
601
|
5
|
|
|
|
|
10
|
my $did = 0;#$level; |
|
602
|
5
|
|
|
|
|
13
|
for my $topic ( qw( title description ) ) { |
|
603
|
10
|
50
|
|
|
|
31
|
next unless $p->{$topic}; |
|
604
|
0
|
0
|
|
|
|
0
|
$s .= "\n" unless $did++; |
|
605
|
|
|
|
|
|
|
$s .= (" " x $i) . "// $_\n" |
|
606
|
0
|
|
|
|
|
0
|
for split( /\s* |\\n|\n/, $p->{$topic} ); |
|
607
|
|
|
|
|
|
|
} |
|
608
|
5
|
|
|
|
|
19
|
return $s; |
|
609
|
17
|
|
|
|
|
129
|
}; |
|
610
|
|
|
|
|
|
|
|
|
611
|
17
|
100
|
|
|
|
47
|
if ( !$level ) { |
|
612
|
5
|
|
|
|
|
40
|
$s .= $comments->($schema); |
|
613
|
|
|
|
|
|
|
} |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
# Format a string value. |
|
616
|
25
|
|
|
25
|
|
39
|
my $pr_string = sub ( $str, $force = 0 ) { |
|
|
25
|
|
|
|
|
44
|
|
|
|
25
|
|
|
|
|
47
|
|
|
|
25
|
|
|
|
|
35
|
|
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
# Reserved strings. |
|
619
|
25
|
50
|
|
|
|
62
|
if ( !defined($str) ) { |
|
620
|
0
|
|
|
|
|
0
|
return "null"; |
|
621
|
|
|
|
|
|
|
} |
|
622
|
|
|
|
|
|
|
|
|
623
|
25
|
100
|
66
|
|
|
186
|
if ( UNIVERSAL::isa( $str, 'JSON::Boolean' ) |
|
624
|
|
|
|
|
|
|
|| UNIVERSAL::isa( $str, 'JSON::PP::Boolean' ) ) { |
|
625
|
1
|
|
|
|
|
70
|
return (qw(false true))[$str]; # force string result |
|
626
|
|
|
|
|
|
|
} |
|
627
|
|
|
|
|
|
|
|
|
628
|
24
|
|
|
|
|
47
|
my $v = $str; |
|
629
|
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Escapes. |
|
631
|
24
|
|
|
|
|
64
|
$v =~ s/\\/\\\\/g; |
|
632
|
24
|
|
|
|
|
43
|
$v =~ s/\n/\\n/g; |
|
633
|
24
|
|
|
|
|
46
|
$v =~ s/\r/\\r/g; |
|
634
|
24
|
|
|
|
|
45
|
$v =~ s/\f/\\f/g; |
|
635
|
24
|
|
|
|
|
49
|
$v =~ s/\013/\\v/g; |
|
636
|
24
|
|
|
|
|
74
|
$v =~ s/\010/\\b/g; |
|
637
|
24
|
|
|
|
|
38
|
$v =~ s/\t/\\t/g; |
|
638
|
24
|
0
|
|
|
|
85
|
$v =~ s/([^ -ÿ])/sprintf( ord($1) < 0xffff ? "\\u%04x" : "\\u{%x}", ord($1))/ge unless $nouesc; |
|
|
0
|
50
|
|
|
|
0
|
|
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
# Force quotes unless the string can be represented as unquoted. |
|
641
|
24
|
100
|
33
|
|
|
614
|
if ( # contains escapes |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
642
|
|
|
|
|
|
|
$v ne $str |
|
643
|
|
|
|
|
|
|
# not value-formed numeric |
|
644
|
|
|
|
|
|
|
|| ( $v =~ /^$p_number$/ && 0+$v ne $v ) |
|
645
|
|
|
|
|
|
|
# contains reserved, quotes or spaces |
|
646
|
|
|
|
|
|
|
|| $v =~ $p_reserved |
|
647
|
|
|
|
|
|
|
|| $v =~ $p_quotes |
|
648
|
|
|
|
|
|
|
|| $v =~ /\s/ |
|
649
|
|
|
|
|
|
|
|| $v =~ /^(true|false|null)$/ |
|
650
|
|
|
|
|
|
|
|| !length($v) |
|
651
|
|
|
|
|
|
|
) { |
|
652
|
13
|
50
|
|
|
|
60
|
if ( $v !~ /\"/ ) { |
|
653
|
13
|
|
|
|
|
54
|
return '"' . $v . '"'; |
|
654
|
|
|
|
|
|
|
} |
|
655
|
0
|
0
|
|
|
|
0
|
if ( $v !~ /\'/ ) { |
|
656
|
0
|
|
|
|
|
0
|
return "'" . $v . "'"; |
|
657
|
|
|
|
|
|
|
} |
|
658
|
0
|
0
|
|
|
|
0
|
if ( $v !~ /\`/ ) { |
|
659
|
0
|
|
|
|
|
0
|
return "`" . $v . "`"; |
|
660
|
|
|
|
|
|
|
} |
|
661
|
0
|
|
|
|
|
0
|
return '"' . ($v =~ s/(["'`])/\\$1/rg) . '"'; |
|
662
|
|
|
|
|
|
|
} |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# Just a string. |
|
665
|
11
|
|
|
|
|
39
|
return $v; |
|
666
|
17
|
|
|
|
|
118
|
}; |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
# Format an array value. |
|
669
|
4
|
|
|
4
|
|
7
|
my $pr_array = sub ( $rv, $level=0, $props = {} ) { |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
7
|
|
|
670
|
4
|
50
|
|
|
|
11
|
return "[]" unless @$rv; |
|
671
|
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
# Gather list of formatted values. |
|
673
|
4
|
|
|
|
|
10
|
my @v = map { $self->encode( %opts, |
|
|
12
|
|
|
|
|
75
|
|
|
674
|
|
|
|
|
|
|
data => $_, |
|
675
|
|
|
|
|
|
|
level => $level+1, |
|
676
|
|
|
|
|
|
|
schema => $props, |
|
677
|
|
|
|
|
|
|
) } @$rv; |
|
678
|
|
|
|
|
|
|
|
|
679
|
4
|
100
|
|
|
|
22
|
return "[".join(",",@v)."]" unless $pretty; |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# If sufficiently short, put it on one line. |
|
682
|
2
|
50
|
33
|
|
|
112
|
if ( $i + length("@v") < 72 |
|
683
|
|
|
|
|
|
|
&& join("",@v) !~ /\s|$p_newlines/ ) { |
|
684
|
2
|
|
|
|
|
16
|
return "[ @v ]"; |
|
685
|
|
|
|
|
|
|
} |
|
686
|
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# Put the values on separate lines. |
|
688
|
0
|
|
|
|
|
0
|
my $s = "[\n"; |
|
689
|
0
|
|
|
|
|
0
|
$s .= s/^/(" " x ($i+$indent))/gemr . "\n" for @v; |
|
|
0
|
|
|
|
|
0
|
|
|
690
|
0
|
|
|
|
|
0
|
$s .= (" " x $i) . "]"; |
|
691
|
|
|
|
|
|
|
|
|
692
|
0
|
|
|
|
|
0
|
return $s; |
|
693
|
17
|
|
|
|
|
107
|
}; |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
# Format a hash value. |
|
696
|
17
|
|
|
8
|
|
30
|
my $pr_hash; $pr_hash = sub ( $rv, $level=0, $props = {} ) { |
|
|
8
|
|
|
|
|
14
|
|
|
|
8
|
|
|
|
|
16
|
|
|
|
8
|
|
|
|
|
17
|
|
|
|
8
|
|
|
|
|
15
|
|
|
|
8
|
|
|
|
|
13
|
|
|
697
|
8
|
50
|
|
|
|
25
|
return "{}" unless keys(%$rv); |
|
698
|
|
|
|
|
|
|
|
|
699
|
8
|
|
|
|
|
17
|
my $s = ""; |
|
700
|
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
# Opening brace. |
|
702
|
8
|
100
|
66
|
|
|
36
|
if ( $level || !$impoh ) { |
|
703
|
3
|
100
|
|
|
|
10
|
$s .= $pretty ? "{\n" : "{"; |
|
704
|
3
|
|
|
|
|
8
|
$i += $indent; |
|
705
|
|
|
|
|
|
|
} |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# If we have a key order, use this and delete. |
|
708
|
|
|
|
|
|
|
my @ko = $rv->{" key order "} |
|
709
|
8
|
100
|
|
|
|
47
|
? @{ delete($rv->{" key order "}) } |
|
|
2
|
|
|
|
|
10
|
|
|
710
|
|
|
|
|
|
|
: sort(keys(%$rv)); |
|
711
|
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
# Dedup. |
|
713
|
8
|
|
|
|
|
39
|
@ko = uniqstr(@ko); |
|
714
|
|
|
|
|
|
|
|
|
715
|
8
|
|
|
|
|
16
|
my $ll = 0; |
|
716
|
8
|
|
|
|
|
19
|
for ( @ko ) { |
|
717
|
|
|
|
|
|
|
# This may be wrong if \ escapes or combined keys are involved. |
|
718
|
10
|
100
|
|
|
|
34
|
$ll = length($_) if length($_) > $ll; |
|
719
|
|
|
|
|
|
|
} |
|
720
|
|
|
|
|
|
|
|
|
721
|
8
|
|
|
|
|
16
|
for ( @ko ) { |
|
722
|
10
|
|
|
|
|
18
|
my $k = $_; |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# Gather comments, if available. |
|
725
|
10
|
|
|
|
|
19
|
my $comment; |
|
726
|
10
|
50
|
|
|
|
29
|
if ( $props->{$k} ) { |
|
727
|
0
|
|
|
|
|
0
|
$comment = $comments->($props->{$k}); |
|
728
|
0
|
0
|
|
|
|
0
|
$s .= $comment if $comment; |
|
729
|
|
|
|
|
|
|
} |
|
730
|
|
|
|
|
|
|
|
|
731
|
10
|
|
|
|
|
22
|
my $v = $rv->{$k}; |
|
732
|
10
|
|
|
|
|
17
|
my $key = $k; # final key |
|
733
|
|
|
|
|
|
|
# Combine keys if allowed and possible. |
|
734
|
10
|
|
66
|
|
|
72
|
while ( $ckeys && ref($v) eq 'HASH' && keys(%$v) == 1 ) { |
|
|
|
|
100
|
|
|
|
|
|
735
|
6
|
|
|
|
|
18
|
my $k = (keys(%$v))[0]; |
|
736
|
6
|
|
|
|
|
13
|
$key .= ".$k"; # append to final key |
|
737
|
6
|
|
|
|
|
32
|
$v = $v->{$k}; # step to next |
|
738
|
|
|
|
|
|
|
} |
|
739
|
|
|
|
|
|
|
|
|
740
|
10
|
100
|
|
|
|
29
|
$s .= (" " x $i) if $pretty; |
|
741
|
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
# Format the key, try to align on length. NEEDS WORK |
|
743
|
10
|
|
|
|
|
23
|
my $t = $pr_string->($key); |
|
744
|
10
|
|
|
|
|
21
|
my $l = length($t); |
|
745
|
10
|
|
|
|
|
21
|
$s .= $t; |
|
746
|
10
|
50
|
|
|
|
48
|
my $in = $comment ? "" : " " x max( 0, $ll-length($t) ); |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
# Handle object serialisation. |
|
749
|
10
|
|
66
|
|
|
58
|
my $r = UNIVERSAL::can( $v, "TO_JSON" ) // UNIVERSAL::can( $v, "FREEZE" ); |
|
750
|
10
|
100
|
|
|
|
64
|
$r = $r ? $v->$r : $v; |
|
751
|
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
# Format the value. |
|
753
|
10
|
100
|
|
|
|
67
|
if ( ref($r) eq 'HASH' ) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
# Make up and recurse. |
|
755
|
3
|
100
|
|
|
|
13
|
if ( $pretty ) { |
|
|
|
50
|
|
|
|
|
|
|
756
|
1
|
50
|
|
|
|
4
|
$s .= $prpmode ? " " : " : "; |
|
757
|
|
|
|
|
|
|
} |
|
758
|
|
|
|
|
|
|
elsif ( !$prpmode ) { |
|
759
|
0
|
|
|
|
|
0
|
$s .= ":"; |
|
760
|
|
|
|
|
|
|
} |
|
761
|
|
|
|
|
|
|
|
|
762
|
3
|
|
|
|
|
89
|
$s .= $pr_hash->( $r, $level+1, $props->{$k}->{properties} ); |
|
763
|
|
|
|
|
|
|
} |
|
764
|
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
elsif ( ref($r) eq 'ARRAY' ) { |
|
766
|
4
|
100
|
|
|
|
12
|
$s .= $pretty ? "$in : " : ":"; |
|
767
|
4
|
|
|
|
|
20
|
$s .= $pr_array->( $r, $level+1, $props->{$k}->{items} ); |
|
768
|
|
|
|
|
|
|
} |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
elsif ( $pretty ) { |
|
771
|
0
|
|
|
|
|
0
|
my $t = $pr_string->($r); |
|
772
|
0
|
|
|
|
|
0
|
$s .= "$in : "; |
|
773
|
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
# Break quoted strings that contain pseudo-newlines. |
|
775
|
0
|
0
|
|
|
|
0
|
if ( $t =~ /^["'`].*\\n/ ) { |
|
776
|
|
|
|
|
|
|
# Remove the quotes/ |
|
777
|
0
|
|
|
|
|
0
|
my $quote = substr( $t, 0, 1, ''); |
|
778
|
0
|
|
|
|
|
0
|
chop($t); |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
# Determine current indent. |
|
781
|
0
|
|
|
|
|
0
|
$s =~ /^(.*)\Z/m; |
|
782
|
0
|
|
|
|
|
0
|
my $sep = " \\\n" . (" " x length($1)); |
|
783
|
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
# Get string parts. |
|
785
|
0
|
|
|
|
|
0
|
my @a = split( /\\n/, $t, -1 ); |
|
786
|
0
|
|
|
|
|
0
|
while ( @a ) { |
|
787
|
0
|
|
|
|
|
0
|
$s .= $quote.shift(@a); |
|
788
|
0
|
0
|
|
|
|
0
|
$s .= "\\n" if @a; |
|
789
|
0
|
|
|
|
|
0
|
$s .= $quote; |
|
790
|
0
|
0
|
|
|
|
0
|
$s .= $sep if @a; |
|
791
|
|
|
|
|
|
|
} |
|
792
|
|
|
|
|
|
|
} |
|
793
|
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
# Just a string. |
|
795
|
|
|
|
|
|
|
else { |
|
796
|
0
|
|
|
|
|
0
|
$s .= $t; |
|
797
|
|
|
|
|
|
|
} |
|
798
|
|
|
|
|
|
|
} |
|
799
|
|
|
|
|
|
|
else { |
|
800
|
3
|
|
|
|
|
8
|
$s .= ":" . $pr_string->($r) . ","; |
|
801
|
|
|
|
|
|
|
} |
|
802
|
10
|
100
|
|
|
|
47
|
$s .= "\n" if $pretty; |
|
803
|
|
|
|
|
|
|
} |
|
804
|
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
# Strip final comma. |
|
806
|
8
|
100
|
|
|
|
47
|
$s =~ s/,$// unless $pretty; |
|
807
|
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
# Closing brace,. |
|
809
|
8
|
100
|
66
|
|
|
31
|
if ( $level || !$impoh ) { |
|
810
|
3
|
|
|
|
|
6
|
$i -= $indent; |
|
811
|
3
|
100
|
|
|
|
10
|
$s .= (" " x $i) if $pretty; |
|
812
|
3
|
|
|
|
|
22
|
$s .= "}"; |
|
813
|
|
|
|
|
|
|
} |
|
814
|
|
|
|
|
|
|
else { |
|
815
|
5
|
|
|
|
|
21
|
$s =~ s/\n+$//; |
|
816
|
|
|
|
|
|
|
} |
|
817
|
|
|
|
|
|
|
|
|
818
|
8
|
|
|
|
|
35
|
return $s; |
|
819
|
17
|
|
|
|
|
287
|
}; |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
# Handle object serialisation. |
|
822
|
17
|
|
33
|
|
|
124
|
my $r = UNIVERSAL::can( $rv, "TO_JSON" ) // UNIVERSAL::can( $rv, "FREEZE" ); |
|
823
|
17
|
50
|
|
|
|
59
|
$r = $r ? $rv->$r : $rv; |
|
824
|
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
# From here it is straight forward. |
|
826
|
17
|
100
|
|
|
|
51
|
if ( ref($r) eq 'HASH' ) { |
|
|
|
50
|
|
|
|
|
|
|
827
|
5
|
|
|
|
|
15
|
$s .= $pr_hash->( $r, $level, $props ); |
|
828
|
|
|
|
|
|
|
} |
|
829
|
|
|
|
|
|
|
elsif ( ref($r) eq 'ARRAY' ) { |
|
830
|
0
|
|
|
|
|
0
|
$s .= $pr_array->( $r, $level ); |
|
831
|
|
|
|
|
|
|
} |
|
832
|
|
|
|
|
|
|
else { |
|
833
|
12
|
|
|
|
|
29
|
$s .= $pr_string->($r); |
|
834
|
|
|
|
|
|
|
} |
|
835
|
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
# Final make-up. |
|
837
|
17
|
|
|
|
|
42
|
$s =~ s/^ +$//gm; |
|
838
|
17
|
100
|
100
|
|
|
51
|
if ( $pretty && !$level ) { |
|
839
|
1
|
|
|
|
|
5
|
$s =~ s/^\n*//s; |
|
840
|
1
|
50
|
|
|
|
5
|
$s .= "\n" if $s !~ /\n$/; |
|
841
|
|
|
|
|
|
|
} |
|
842
|
17
|
|
|
|
|
118
|
return $s; |
|
843
|
|
|
|
|
|
|
} |
|
844
|
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
################ Subroutines ################ |
|
846
|
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
# resolve processes $ref, allOf etc nodes. |
|
848
|
|
|
|
|
|
|
|
|
849
|
0
|
|
|
0
|
0
|
0
|
sub resolve( $d, $schema ) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
850
|
|
|
|
|
|
|
|
|
851
|
0
|
0
|
|
|
|
0
|
if ( is_hash($d) ) { |
|
|
|
0
|
|
|
|
|
|
|
852
|
0
|
|
|
|
|
0
|
while ( my ($k,$v) = each %$d ) { |
|
853
|
0
|
0
|
0
|
|
|
0
|
if ( $k eq 'allOf' ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
854
|
0
|
|
|
|
|
0
|
delete $d->{$k}; # yes, safe to do |
|
855
|
0
|
|
|
|
|
0
|
$d = merge( resolve( $_, $schema ), $d ) for @$v; |
|
856
|
|
|
|
|
|
|
} |
|
857
|
|
|
|
|
|
|
elsif ( $k eq 'oneOf' || $k eq 'anyOf' ) { |
|
858
|
0
|
|
|
|
|
0
|
delete $d->{$k}; # yes, safe to do |
|
859
|
0
|
|
|
|
|
0
|
$d = merge( resolve( $v->[0], $schema ), $d ); |
|
860
|
|
|
|
|
|
|
} |
|
861
|
|
|
|
|
|
|
elsif ( $k eq '$ref' ) { |
|
862
|
0
|
|
|
|
|
0
|
delete $d->{$k}; # yes, safe to do |
|
863
|
0
|
0
|
|
|
|
0
|
if ( $v =~ m;^#/definitions/(.*); ) { |
|
864
|
0
|
|
|
|
|
0
|
$d = merge( resolve( $schema->{definitions}->{$1}, $schema ), $d ); |
|
865
|
|
|
|
|
|
|
} |
|
866
|
|
|
|
|
|
|
else { |
|
867
|
0
|
|
|
|
|
0
|
die("Invalid \$ref: $v\n"); |
|
868
|
|
|
|
|
|
|
} |
|
869
|
|
|
|
|
|
|
} |
|
870
|
|
|
|
|
|
|
else { |
|
871
|
0
|
|
|
|
|
0
|
$d->{$k} = resolve( $v, $schema ); |
|
872
|
|
|
|
|
|
|
} |
|
873
|
|
|
|
|
|
|
} |
|
874
|
|
|
|
|
|
|
} |
|
875
|
|
|
|
|
|
|
elsif ( is_array($d) ) { |
|
876
|
0
|
|
|
|
|
0
|
$d = [ map { resolve( $_, $schema ) } @$d ]; |
|
|
0
|
|
|
|
|
0
|
|
|
877
|
|
|
|
|
|
|
} |
|
878
|
|
|
|
|
|
|
else { |
|
879
|
|
|
|
|
|
|
} |
|
880
|
|
|
|
|
|
|
|
|
881
|
0
|
|
|
|
|
0
|
return $d; |
|
882
|
|
|
|
|
|
|
} |
|
883
|
|
|
|
|
|
|
|
|
884
|
0
|
|
|
0
|
0
|
0
|
sub is_hash($o) { UNIVERSAL::isa( $o, 'HASH' ) } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
885
|
0
|
|
|
0
|
0
|
0
|
sub is_array($o) { UNIVERSAL::isa( $o, 'ARRAY' ) } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
886
|
|
|
|
|
|
|
|
|
887
|
0
|
|
|
0
|
0
|
0
|
sub merge ( $left, $right ) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
888
|
|
|
|
|
|
|
|
|
889
|
0
|
0
|
|
|
|
0
|
return $left unless $right; |
|
890
|
|
|
|
|
|
|
|
|
891
|
0
|
|
|
|
|
0
|
my %merged = %$left; |
|
892
|
|
|
|
|
|
|
|
|
893
|
0
|
|
|
|
|
0
|
for my $key ( keys %$right ) { |
|
894
|
|
|
|
|
|
|
|
|
895
|
0
|
|
|
|
|
0
|
my ($hr, $hl) = map { is_hash($_->{$key}) } $right, $left; |
|
|
0
|
|
|
|
|
0
|
|
|
896
|
|
|
|
|
|
|
|
|
897
|
0
|
0
|
0
|
|
|
0
|
if ( $hr and $hl ) { |
|
898
|
0
|
|
|
|
|
0
|
$merged{$key} = merge( $left->{$key}, $right->{$key} ); |
|
899
|
|
|
|
|
|
|
} |
|
900
|
|
|
|
|
|
|
else { |
|
901
|
0
|
|
|
|
|
0
|
$merged{$key} = $right->{$key}; |
|
902
|
|
|
|
|
|
|
} |
|
903
|
|
|
|
|
|
|
} |
|
904
|
|
|
|
|
|
|
|
|
905
|
0
|
|
|
|
|
0
|
return \%merged; |
|
906
|
|
|
|
|
|
|
} |
|
907
|
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
################ Tokens ################ |
|
909
|
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
class JSON::Relaxed::Token; |
|
911
|
|
|
|
|
|
|
|
|
912
|
123
|
50
|
|
123
|
|
245
|
field $parent :accessor :param; |
|
|
123
|
|
|
|
|
443
|
|
|
913
|
1023
|
50
|
|
1023
|
|
1577
|
field $token :accessor :param; |
|
|
1023
|
|
|
|
|
3060
|
|
|
914
|
0
|
0
|
|
0
|
|
0
|
field $type :accessor :param; |
|
|
0
|
|
|
|
|
0
|
|
|
915
|
18
|
50
|
|
18
|
|
43
|
field $offset :accessor :param; |
|
|
18
|
|
|
|
|
121
|
|
|
916
|
|
|
|
|
|
|
|
|
917
|
648
|
|
|
648
|
|
823
|
method is_string() { |
|
|
648
|
|
|
|
|
1146
|
|
|
|
648
|
|
|
|
|
756
|
|
|
918
|
648
|
|
|
|
|
2193
|
$type =~ /[QUN]/ |
|
919
|
|
|
|
|
|
|
} |
|
920
|
|
|
|
|
|
|
|
|
921
|
234
|
|
|
234
|
|
329
|
method is_list_opener() { |
|
|
234
|
|
|
|
|
355
|
|
|
|
234
|
|
|
|
|
269
|
|
|
922
|
234
|
100
|
|
|
|
1428
|
$type eq 'C' && $token =~ /[{\[]/; |
|
923
|
|
|
|
|
|
|
} |
|
924
|
|
|
|
|
|
|
|
|
925
|
0
|
|
|
0
|
|
0
|
method as_perl( %options ) { # for values |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
926
|
0
|
|
|
|
|
0
|
$token->as_perl(%options); |
|
927
|
|
|
|
|
|
|
} |
|
928
|
|
|
|
|
|
|
|
|
929
|
0
|
|
|
0
|
|
0
|
method _data_printer( $ddp ) { # for DDP |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
930
|
0
|
|
|
|
|
0
|
my $res = "Token("; |
|
931
|
0
|
0
|
|
|
|
0
|
if ( !defined $token ) { |
|
|
|
0
|
|
|
|
|
|
|
932
|
0
|
|
|
|
|
0
|
$res .= "null"; |
|
933
|
|
|
|
|
|
|
} |
|
934
|
|
|
|
|
|
|
elsif ( $self->is_string ) { |
|
935
|
0
|
|
|
|
|
0
|
$res .= $token->_data_printer($ddp); |
|
936
|
|
|
|
|
|
|
} |
|
937
|
|
|
|
|
|
|
else { |
|
938
|
0
|
|
|
|
|
0
|
$res .= "\"$token\""; |
|
939
|
|
|
|
|
|
|
} |
|
940
|
0
|
|
|
|
|
0
|
$res .= ", $type"; |
|
941
|
0
|
|
|
|
|
0
|
$res . ", $offset)"; |
|
942
|
|
|
|
|
|
|
} |
|
943
|
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
method as_string { # for messages |
|
945
|
|
|
|
|
|
|
my $res = ""; |
|
946
|
|
|
|
|
|
|
if ( $self->is_string ) { |
|
947
|
|
|
|
|
|
|
$res = '"' . ($self->content =~ s/"/\\"/gr) . '"'; |
|
948
|
|
|
|
|
|
|
} |
|
949
|
|
|
|
|
|
|
else { |
|
950
|
|
|
|
|
|
|
$res .= "\"$token\""; |
|
951
|
|
|
|
|
|
|
} |
|
952
|
|
|
|
|
|
|
$res; |
|
953
|
|
|
|
|
|
|
} |
|
954
|
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
=begin heavily_optimized_alternative |
|
956
|
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
package JSON::Relaxed::XXToken; |
|
958
|
|
|
|
|
|
|
our @ISA = qw(JSON::Relaxed::Parser); |
|
959
|
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
sub new { |
|
961
|
|
|
|
|
|
|
my ( $pkg, %opts ) = @_; |
|
962
|
|
|
|
|
|
|
my $self = bless [] => $pkg; |
|
963
|
|
|
|
|
|
|
push( @$self, |
|
964
|
|
|
|
|
|
|
delete(%opts{parent}), |
|
965
|
|
|
|
|
|
|
delete(%opts{token}), |
|
966
|
|
|
|
|
|
|
delete(%opts{type}), |
|
967
|
|
|
|
|
|
|
delete(%opts{offset}), |
|
968
|
|
|
|
|
|
|
); |
|
969
|
|
|
|
|
|
|
$self; |
|
970
|
|
|
|
|
|
|
} |
|
971
|
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
sub parent { $_[0]->[0] } |
|
973
|
|
|
|
|
|
|
sub token { $_[0]->[1] } |
|
974
|
|
|
|
|
|
|
sub type { $_[0]->[2] } |
|
975
|
|
|
|
|
|
|
sub offset { $_[0]->[3] } |
|
976
|
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
sub is_string { $_[0]->[2] =~ /[QUN]/ } |
|
978
|
|
|
|
|
|
|
sub is_list_opener { $_[0]->[2] eq 'C' && $_[0]->[1] =~ /[{\[]/ } |
|
979
|
|
|
|
|
|
|
sub as_perl { # for values |
|
980
|
|
|
|
|
|
|
return shift->[1]->as_perl(@_); |
|
981
|
|
|
|
|
|
|
} |
|
982
|
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
sub _data_printer { # for DDP |
|
984
|
|
|
|
|
|
|
my ( $self, $ddp ) = @_; |
|
985
|
|
|
|
|
|
|
my $res = "Token("; |
|
986
|
|
|
|
|
|
|
if ( $self->is_string ) { |
|
987
|
|
|
|
|
|
|
$res .= $self->[1]->_data_printer($ddp); |
|
988
|
|
|
|
|
|
|
} |
|
989
|
|
|
|
|
|
|
else { |
|
990
|
|
|
|
|
|
|
$res .= "\"".$self->[1]."\""; |
|
991
|
|
|
|
|
|
|
} |
|
992
|
|
|
|
|
|
|
$res .= ", " . $self->[2]; |
|
993
|
|
|
|
|
|
|
$res . ", " . $self->[3] . ")"; |
|
994
|
|
|
|
|
|
|
} |
|
995
|
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
sub as_string { # for messages |
|
997
|
|
|
|
|
|
|
if ( $_[0]->is_string ) { |
|
998
|
|
|
|
|
|
|
return '"' . ($_[0]->[1]->content =~ s/"/\\"/gr) . '"'; |
|
999
|
|
|
|
|
|
|
} |
|
1000
|
|
|
|
|
|
|
"\"" . $_[0]->[1] . "\""; |
|
1001
|
|
|
|
|
|
|
} |
|
1002
|
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
=cut |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
################ Strings ################ |
|
1006
|
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
class JSON::Relaxed::String :isa(JSON::Relaxed::Token); |
|
1008
|
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
field $content :param = undef; |
|
1010
|
|
|
|
|
|
|
field $quote :accessor :param = undef; |
|
1011
|
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
# Quoted strings are assembled from complete substrings, so escape |
|
1013
|
|
|
|
|
|
|
# processing is done on the substrings. This prevents ugly things |
|
1014
|
|
|
|
|
|
|
# when unicode escapes are split across substrings. |
|
1015
|
|
|
|
|
|
|
# Unquotes strings are collected token by token, so escape processing |
|
1016
|
|
|
|
|
|
|
# can only be done on the complete string (on output). |
|
1017
|
|
|
|
|
|
|
|
|
1018
|
0
|
0
|
|
0
|
|
0
|
ADJUST { |
|
|
0
|
|
|
|
|
0
|
|
|
1019
|
|
|
|
|
|
|
$content = $self->unescape($content) if defined($quote); |
|
1020
|
|
|
|
|
|
|
}; |
|
1021
|
|
|
|
|
|
|
|
|
1022
|
25
|
|
|
25
|
|
43
|
method append ($str) { |
|
|
25
|
|
|
|
|
52
|
|
|
|
25
|
|
|
|
|
40
|
|
|
|
25
|
|
|
|
|
35
|
|
|
1023
|
25
|
100
|
|
|
|
62
|
$str = $self->unescape($str) if defined $quote; |
|
1024
|
25
|
|
|
|
|
61
|
$content .= $str; |
|
1025
|
|
|
|
|
|
|
} |
|
1026
|
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
method content { |
|
1028
|
|
|
|
|
|
|
defined($quote) ? $content : $self->unescape($content); |
|
1029
|
|
|
|
|
|
|
} |
|
1030
|
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
# One regexp to match them all... |
|
1032
|
|
|
|
|
|
|
my $esc_quoted = qr/ |
|
1033
|
|
|
|
|
|
|
\\([tnrfb]) # $1 : one char |
|
1034
|
|
|
|
|
|
|
| \\u\{([[:xdigit:]]+)\} # $2 : \u{XX...} |
|
1035
|
|
|
|
|
|
|
| \\u([Dd][89abAB][[:xdigit:]]{2}) # $3 : \uDXXX hi |
|
1036
|
|
|
|
|
|
|
\\u([Dd][c-fC-F][[:xdigit:]]{2}) # $4 : \uDXXX lo |
|
1037
|
|
|
|
|
|
|
| \\u([[:xdigit:]]{4}) # $5 : \uXXXX |
|
1038
|
|
|
|
|
|
|
| \\?(.) # $6 |
|
1039
|
|
|
|
|
|
|
/xs; |
|
1040
|
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
# Special escapes (quoted strings only). |
|
1042
|
|
|
|
|
|
|
my %esc = ( |
|
1043
|
|
|
|
|
|
|
'b' => "\b", # Backspace |
|
1044
|
|
|
|
|
|
|
'f' => "\f", # Form feed |
|
1045
|
|
|
|
|
|
|
'n' => "\n", # New line |
|
1046
|
|
|
|
|
|
|
'r' => "\r", # Carriage return |
|
1047
|
|
|
|
|
|
|
't' => "\t", # Tab |
|
1048
|
|
|
|
|
|
|
'v' => chr(11), # Vertical tab |
|
1049
|
|
|
|
|
|
|
); |
|
1050
|
|
|
|
|
|
|
|
|
1051
|
365
|
|
|
365
|
|
485
|
method unescape ($str) { |
|
|
365
|
|
|
|
|
625
|
|
|
|
365
|
|
|
|
|
512
|
|
|
|
365
|
|
|
|
|
420
|
|
|
1052
|
365
|
100
|
|
|
|
1433
|
return $str unless $str =~ /\\/; |
|
1053
|
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
my $convert = sub { |
|
1055
|
|
|
|
|
|
|
# Specials. Only for quoted strings. |
|
1056
|
560
|
100
|
|
560
|
|
1009
|
if ( defined($1) ) { |
|
1057
|
3
|
50
|
|
|
|
12
|
return defined($quote) ? $esc{$1} : $1; |
|
1058
|
|
|
|
|
|
|
} |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
# Extended \u{XXX} character. |
|
1061
|
557
|
100
|
|
|
|
975
|
defined($2) and return chr(hex($2)); |
|
1062
|
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
# Pair of surrogates. |
|
1064
|
550
|
100
|
|
|
|
973
|
defined($3) and return pack( 'U*', |
|
1065
|
|
|
|
|
|
|
0x10000 + (hex($3) - 0xD800) * 0x400 |
|
1066
|
|
|
|
|
|
|
+ (hex($4) - 0xDC00) ); |
|
1067
|
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
# Standard \uXXXX character. |
|
1069
|
542
|
100
|
|
|
|
970
|
defined($5) and return chr(hex($5)); |
|
1070
|
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
# Anything else. |
|
1072
|
528
|
50
|
|
|
|
1850
|
defined($6) and return $6; |
|
1073
|
|
|
|
|
|
|
|
|
1074
|
0
|
|
|
|
|
0
|
return ''; |
|
1075
|
58
|
|
|
|
|
302
|
}; |
|
1076
|
|
|
|
|
|
|
|
|
1077
|
58
|
|
|
|
|
1019
|
while( $str =~ s/\G$esc_quoted/$convert->()/gxse) { |
|
|
560
|
|
|
|
|
812
|
|
|
1078
|
58
|
50
|
|
|
|
217
|
last unless defined pos($str); |
|
1079
|
|
|
|
|
|
|
} |
|
1080
|
|
|
|
|
|
|
|
|
1081
|
58
|
|
|
|
|
628
|
return $str; |
|
1082
|
|
|
|
|
|
|
} |
|
1083
|
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
################ Quoted Strings ################ |
|
1085
|
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
class JSON::Relaxed::String::Quoted :isa(JSON::Relaxed::String); |
|
1087
|
|
|
|
|
|
|
|
|
1088
|
185
|
|
|
185
|
|
229
|
method as_perl( %options ) { |
|
|
185
|
|
|
|
|
392
|
|
|
|
185
|
|
|
|
|
250
|
|
|
|
185
|
|
|
|
|
207
|
|
|
1089
|
185
|
|
|
|
|
486
|
$self->content; |
|
1090
|
|
|
|
|
|
|
} |
|
1091
|
|
|
|
|
|
|
|
|
1092
|
0
|
|
|
0
|
|
0
|
method _data_printer( $ddp ) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
1093
|
0
|
|
|
|
|
0
|
"Token(" . $self->quote . $self->content . $self->quote . ", " . |
|
1094
|
|
|
|
|
|
|
$self->type . ", " . $self->offset . ")"; |
|
1095
|
|
|
|
|
|
|
} |
|
1096
|
|
|
|
|
|
|
|
|
1097
|
|
|
|
|
|
|
################ Unquoted Strings ################ |
|
1098
|
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
class JSON::Relaxed::String::Unquoted :isa(JSON::Relaxed::String); |
|
1100
|
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
# If the option always_string is set, bypass the reserved strings. |
|
1102
|
|
|
|
|
|
|
# This is used for hash keys. |
|
1103
|
163
|
|
|
163
|
|
241
|
method as_perl( %options ) { |
|
|
163
|
|
|
|
|
287
|
|
|
|
163
|
|
|
|
|
258
|
|
|
|
163
|
|
|
|
|
251
|
|
|
1104
|
163
|
|
|
|
|
492
|
my $content = $self->content; |
|
1105
|
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
# If used as a key, always return a string. |
|
1107
|
163
|
100
|
|
|
|
472
|
return $content if $options{always_string}; |
|
1108
|
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
# Return boolean specials if appropriate. |
|
1110
|
113
|
100
|
|
|
|
394
|
if ( $content =~ /^(?:true|false)$/ ) { |
|
1111
|
14
|
100
|
|
|
|
37
|
return $self->parent->booleans->[ $content eq 'true' ? 1 : 0 ]; |
|
1112
|
|
|
|
|
|
|
} |
|
1113
|
99
|
100
|
66
|
|
|
256
|
if ( $self->parent->prp && $content =~ /^(?:on|off)$/ ) { |
|
|
|
100
|
|
|
|
|
|
|
1114
|
4
|
100
|
|
|
|
10
|
return $self->parent->booleans->[ $content eq 'on' ? 1 : 0 ]; |
|
1115
|
|
|
|
|
|
|
} |
|
1116
|
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
# null -> undef |
|
1118
|
|
|
|
|
|
|
elsif ( $content eq "null" ) { |
|
1119
|
4
|
|
|
|
|
9
|
return undef; |
|
1120
|
|
|
|
|
|
|
} |
|
1121
|
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
# Return as string. |
|
1123
|
91
|
|
|
|
|
306
|
$content; |
|
1124
|
|
|
|
|
|
|
} |
|
1125
|
|
|
|
|
|
|
|
|
1126
|
0
|
|
|
0
|
|
|
method _data_printer( $ddp ) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
1127
|
0
|
|
|
|
|
|
"Token(«" . $self->content . "», " . |
|
1128
|
|
|
|
|
|
|
$self->type . ", " . $self->offset . ")"; |
|
1129
|
|
|
|
|
|
|
} |
|
1130
|
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
################ Booleans ################ |
|
1132
|
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
# This class distinguises booleans true and false from numeric 1 and 0. |
|
1134
|
|
|
|
|
|
|
|
|
1135
|
11
|
|
|
11
|
|
79656
|
use JSON::PP (); |
|
|
11
|
|
|
|
|
429986
|
|
|
|
11
|
|
|
|
|
6335
|
|
|
1136
|
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
package JSON::Boolean { |
|
1138
|
|
|
|
|
|
|
|
|
1139
|
0
|
|
|
0
|
|
|
sub as_perl( $self, %options ) { $self } |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
|
|
1141
|
0
|
|
|
0
|
|
|
sub _data_printer( $self, $ddp ) { "Bool($self)" } |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
|
|
1143
|
8
|
100
|
|
8
|
|
1173
|
use overload '""' => sub { ${$_[0]} ? "true" : "false" }, |
|
|
8
|
|
|
|
|
179
|
|
|
1144
|
1
|
|
|
1
|
|
4
|
"0+" => sub { ${$_[0]} }, |
|
|
1
|
|
|
|
|
8
|
|
|
1145
|
4
|
|
|
4
|
|
1897
|
"bool" => sub { !!${$_[0]} }, |
|
|
4
|
|
|
|
|
25
|
|
|
1146
|
11
|
|
|
11
|
|
129
|
fallback => 1; |
|
|
11
|
|
|
|
|
29
|
|
|
|
11
|
|
|
|
|
165
|
|
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
# For JSON::PP export. |
|
1149
|
0
|
0
|
|
0
|
|
|
sub TO_JSON { ${$_[0]} ? $JSON::PP::true : $JSON::PP::false } |
|
|
0
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
# Boolean values. |
|
1152
|
|
|
|
|
|
|
our $true = do { bless \(my $dummy = 1) => __PACKAGE__ }; |
|
1153
|
|
|
|
|
|
|
our $false = do { bless \(my $dummy = 0) => __PACKAGE__ }; |
|
1154
|
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
} |
|
1156
|
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
################ |
|
1158
|
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
1; |