1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
4 # Copyright 2014 Alexandre Terrasa <alexandre@moz-concept.com>
5 # Copyright 2014 Jean-Christophe Beaupré <jcbrinfo@users.noreply.github.com>
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # Static interface to read Nit objects from JSON strings
21 # `Text::parse_json` returns a simple Nit object from the JSON source.
22 # This object can then be type checked as usual with `isa` and `as`.
30 # Removes JSON-escaping if necessary in a JSON string
32 # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
33 fun unescape_json
: Text do
34 if not json_need_escape
then return self
35 return self.json_to_nit_string
38 # Does `self` need treatment from JSON to Nit ?
40 # i.e. is there at least one `\` character in it ?
42 # assert not "string".json_need_escape
43 # assert "\\\"string\\\"".json_need_escape
44 private fun json_need_escape
: Bool do return has
('\\')
46 # Escapes `self` from a JSON string to a Nit string
48 # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
49 # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
50 # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
51 private fun json_to_nit_string
: String do
52 var res
= new FlatBuffer.with_capacity
(byte_length
)
61 char
= 0x08.code_point
62 else if char
== 'f' then
63 char
= 0x0C.code_point
64 else if char
== 'n' then
66 else if char
== 'r' then
68 else if char
== 't' then
70 else if char
== 'u' then
71 var u16_esc
= from_utf16_digit
(i
+ 1)
72 char
= u16_esc
.code_point
73 if char
.is_surrogate
and i
+ 10 < ln
then
74 if self[i
+ 5] == '\\' and self[i
+ 6] == 'u' then
76 u16_esc
+= from_utf16_digit
(i
+ 7)
77 char
= u16_esc
.to_u32
.from_utf16_surr
.code_point
80 char
= 0xFFFD.code_point
85 # `"`, `/` or `\` => Keep `char` as-is.
93 # Parse `self` as JSON.
95 # If `self` is not a valid JSON document or contains an unsupported escape
96 # sequence, return a `JSONParseError`.
98 # Example with `JsonObject`:
100 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
101 # assert obj isa JsonObject
102 # assert obj["foo"] isa JsonObject
103 # assert obj["foo"].as(JsonObject)["bar"] == true
105 # Example with `JsonArray`:
107 # var arr = "[1, 2, 3]".parse_json
108 # assert arr isa JsonArray
109 # assert arr.length == 3
110 # assert arr.first == 1
111 # assert arr.last == 3
113 # Example with `String`:
115 # var str = "\"foo, bar, baz\"".parse_json
116 # assert str isa String
117 # assert str == "foo, bar, baz"
119 # Example of a syntax error:
121 # var error = "\{foo: \"bar\"\}".parse_json
122 # assert error isa JsonParseError
123 # assert error.to_s == "Bad key format Error: bad JSON entity"
124 fun parse_json
: nullable Serializable do return (new JSONStringParser(self.to_s
)).parse_entity
128 redef fun json_need_escape
do
130 for i
in [first_byte
.. last_byte
] do
131 if its
[i
] == 0x5C then return true
138 # Is `self` a valid number start ?
139 private fun is_json_num_start
: Bool do
140 if self == '-' then return true
141 if self.is_numeric
then return true
145 # Is `self` a valid JSON separator ?
146 private fun is_json_separator
: Bool do
147 if self == ':' then return true
148 if self == ',' then return true
149 if self == '{' then return true
150 if self == '}' then return true
151 if self == '[' then return true
152 if self == ']' then return true
153 if self == '"' then return true
154 if self.is_whitespace
then return true
159 # A simple ad-hoc JSON parser
161 # To parse a simple JSON document, read it as a String and give it to `parse_entity`
162 # NOTE: if your document contains several non-nested entities, use `parse_entity` for each
163 # JSON entity to parse
164 class JSONStringParser
165 super StringProcessor
167 # Parses a JSON Entity
170 # var p = new JSONStringParser("""{"numbers": [1,23,3], "string": "string"}""")
171 # assert p.parse_entity isa JsonObject
173 fun parse_entity
: nullable Serializable do
176 if pos
>= srclen
then return make_parse_error
("Empty JSON")
180 return parse_json_array
181 else if c
== '"' then
182 var s
= parse_json_string
184 else if c
== '{' then
186 return parse_json_object
187 else if c
== 'f' then
188 if pos
+ 4 >= srclen
then make_parse_error
("Error: bad JSON entity")
189 if src
[pos
+ 1] == 'a' and src
[pos
+ 2] == 'l' and src
[pos
+ 3] == 's' and src
[pos
+ 4] == 'e' then
193 return make_parse_error
("Error: bad JSON entity")
194 else if c
== 't' then
195 if pos
+ 3 >= srclen
then make_parse_error
("Error: bad JSON entity")
196 if src
[pos
+ 1] == 'r' and src
[pos
+ 2] == 'u' and src
[pos
+ 3] == 'e' then
200 return make_parse_error
("Error: bad JSON entity")
201 else if c
== 'n' then
202 if pos
+ 3 >= srclen
then make_parse_error
("Error: bad JSON entity")
203 if src
[pos
+ 1] == 'u' and src
[pos
+ 2] == 'l' and src
[pos
+ 3] == 'l' then
207 return make_parse_error
("Error: bad JSON entity")
209 if not c
.is_json_num_start
then return make_parse_error
("Bad JSON character")
210 return parse_json_number
213 # Parses a JSON Array
214 fun parse_json_array
: Serializable do
216 if pos
>= max
then return make_parse_error
("Incomplete JSON array")
217 var arr
= new JsonArray
219 while not c
== ']' do
221 if pos
>= max
then return make_parse_error
("Incomplete JSON array")
222 if src
[pos
] == ']' then break
223 var ent
= parse_entity
224 #print "Parsed an entity {ent} for a JSON array"
225 if ent
isa JsonParseError then return ent
228 if pos
>= max
then return make_parse_error
("Incomplete JSON array")
230 if c
== ']' then break
231 if c
!= ',' then return make_parse_error
("Bad array separator {c}")
238 # Parses a JSON Object
239 fun parse_json_object
: Serializable do
241 if pos
>= max
then return make_parse_error
("Incomplete JSON object")
242 var obj
= new JsonObject
244 while not c
== '}' do
246 if pos
>= max
then return make_parse_error
("Malformed JSON object")
247 if src
[pos
] == '}' then break
248 var key
= parse_entity
249 #print "Parsed key {key} for JSON object"
250 if not key
isa String then return make_parse_error
("Bad key format {key or else "null"}")
252 if pos
>= max
then return make_parse_error
("Incomplete JSON object")
253 if not src
[pos
] == ':' then return make_parse_error
("Bad key/value separator {src[pos]}")
256 var value
= parse_entity
257 #print "Parsed value {value} for JSON object"
258 if value
isa JsonParseError then return value
261 if pos
>= max
then return make_parse_error
("Incomplete JSON object")
263 if c
== '}' then break
264 if c
!= ',' then return make_parse_error
("Bad object separator {src[pos]}")
271 # Creates a `JsonParseError` with the right message and location
272 protected fun make_parse_error
(message
: String): JsonParseError do
273 var err
= new JsonParseError(message
)
274 err
.location
= hot_location
278 # Parses an Int or Float
279 fun parse_json_number
: Serializable do
287 if p
>= max
then return make_parse_error
("Bad JSON number")
291 while c
.is_numeric
do
295 if p
>= max
then break
300 if p
>= max
then return make_parse_error
("Bad JSON number")
304 while c
.is_numeric
do
305 fl
+= c
.to_i
.to_f
* frac
308 if p
>= max
then break
311 if c
== 'e' or c
== 'E' then
314 if p
>= max
then return make_parse_error
("Malformed JSON number")
316 while c
.is_numeric
do
320 if p
>= max
then break
323 fl
*= (10 ** exp
).to_f
325 if p
< max
and not c
.is_json_separator
then return make_parse_error
("Malformed JSON number")
327 if is_neg
then return -fl
330 if c
== 'e' or c
== 'E' then
332 if p
>= max
then return make_parse_error
("Bad JSON number")
333 var exp
= src
[p
].to_i
335 while c
.is_numeric
do
339 if p
>= max
then break
344 if p
< max
and not src
[p
].is_json_separator
then return make_parse_error
("Malformed JSON number")
346 if is_neg
then return -val
350 private var parse_str_buf
= new FlatBuffer
352 # Parses and returns a Nit string from a JSON String
353 fun parse_json_string
: Serializable do
358 if p
> ln
then return make_parse_error
("Malformed JSON String")
360 var ret
= parse_str_buf
365 if p
>= ln
then return make_parse_error
("Malformed JSON string")
369 ret
.append_substring_impl
(src
, chunk_st
, p
- chunk_st
)
371 if p
>= ln
then return make_parse_error
("Malformed Escape sequence in JSON string")
376 else if c
== 'n' then
379 else if c
== 't' then
382 else if c
== 'u' then
387 if p
>= ln
then make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
389 if c
>= '0' and c
<= '9' then
390 cp
+= c
.code_point
- '0'.code_point
391 else if c
>= 'a' and c
<= 'f' then
392 cp
+= c
.code_point
- 'a'.code_point
+ 10
393 else if c
>= 'A' and c
<= 'F' then
394 cp
+= c
.code_point
- 'A'.code_point
+ 10
396 make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
401 if cp
>= 0xD800 and cp
<= 0xDBFF then
402 if p
>= ln
then make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
404 if c
!= '\\' then make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
407 if c
!= 'u' then make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
412 if p
> ln
then make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
414 if c
>= '0' and c
<= '9' then
415 locp
+= c
.code_point
- '0'.code_point
416 else if c
>= 'a' and c
<= 'f' then
417 locp
+= c
.code_point
- 'a'.code_point
+ 10
418 else if c
>= 'A' and c
<= 'F' then
419 locp
+= c
.code_point
- 'A'.code_point
+ 10
421 make_parse_error
("Malformed \uXXXX Escape sequence in JSON string")
425 c
= (((locp
& 0x3FF) | ((cp
& 0x3FF) << 10)) + 0x10000).code_point
428 else if c
== 'b' then
431 else if c
== 'f' then
442 if ret
.is_empty
then return src
.substring
(chunk_st
, p
- chunk_st
)
443 ret
.append_substring_impl
(src
, chunk_st
, p
- chunk_st
)
449 # Ignores any character until a JSON separator is encountered
450 fun ignore_until_separator
do
453 if not src
[pos
].is_json_separator
then return
458 # A map that can be translated into a JSON object.
459 interface JsonMapRead[K
: String, V
: nullable Serializable]
466 super JsonMapRead[String, nullable Serializable]
467 super HashMap[String, nullable Serializable]
470 # A sequence that can be translated into a JSON array.
471 class JsonSequenceRead[E
: nullable Serializable]
473 super SequenceRead[E
]
478 super JsonSequenceRead[nullable Serializable]
479 super Array[nullable Serializable]