json/static: Fixed Unicode escaping in JSON read code
[nit.git] / lib / json / static.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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>
6 #
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
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
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.
18
19 # Static interface to get Nit objects from a Json string.
20 #
21 # `Text::parse_json` returns an equivalent Nit object from
22 # the Json source. This object can then be type checked by the usual
23 # languages features (`isa` and `as`).
24 module static
25
26 import error
27 private import json_parser
28 private import json_lexer
29
30 # Something that can be translated to JSON.
31 interface Jsonable
32 # Encode `self` in JSON.
33 #
34 # SEE: `append_json`
35 fun to_json: String is abstract
36
37 # Use `append_json` to implement `to_json`.
38 #
39 # Therefore, one that redefine `append_json` may use the following
40 # redefinition to link `to_json` and `append_json`:
41 #
42 # ~~~nitish
43 # redef fun to_json do return to_json_by_append
44 # ~~~
45 #
46 # Note: This is not the default implementation of `to_json` in order to
47 # avoid cyclic references between `append_json` and `to_json` when none are
48 # implemented.
49 protected fun to_json_by_append: String do
50 var buffer = new RopeBuffer
51 append_json(buffer)
52 return buffer.write_to_string
53 end
54
55 # Append the JSON representation of `self` to the specified buffer.
56 #
57 # SEE: `to_json`
58 fun append_json(buffer: Buffer) do buffer.append(to_json)
59
60 # Pretty print JSON string.
61 #
62 # ~~~
63 # var obj = new JsonObject
64 # obj["foo"] = 1
65 # obj["bar"] = true
66 # var arr = new JsonArray
67 # arr.add 2
68 # arr.add false
69 # arr.add "baz"
70 # obj["baz"] = arr
71 # var res = obj.to_pretty_json
72 # var exp = """{
73 # \t"foo": 1,
74 # \t"bar": true,
75 # \t"baz": [2, false, "baz"]
76 # }\n"""
77 # assert res == exp
78 # ~~~
79 fun to_pretty_json: String do
80 var res = new FlatBuffer
81 pretty_json_visit(res, 0)
82 res.add '\n'
83 return res.write_to_string
84 end
85
86 private fun pretty_json_visit(buffer: FlatBuffer, indent: Int) is abstract
87 end
88
89 redef class Text
90 super Jsonable
91
92 redef fun append_json(buffer) do
93 buffer.add '\"'
94 for i in [0 .. self.length[ do
95 var char = self[i]
96 if char == '\\' then
97 buffer.append "\\\\"
98 else if char == '\"' then
99 buffer.append "\\\""
100 else if char == '\/' then
101 buffer.append "\\/"
102 else if char < ' ' then
103 if char == '\n' then
104 buffer.append "\\n"
105 else if char == '\r' then
106 buffer.append "\\r"
107 else if char == '\t' then
108 buffer.append "\\t"
109 else if char == 0x0C.code_point then
110 buffer.append "\\f"
111 else if char == 0x08.code_point then
112 buffer.append "\\b"
113 else
114 buffer.append char.escape_to_utf16
115 end
116 else
117 buffer.add char
118 end
119 end
120 buffer.add '\"'
121 end
122
123 # Encode `self` in JSON.
124 #
125 # ~~~
126 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
127 # "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
128 # ~~~
129 redef fun to_json do return to_json_by_append
130
131 # Parse `self` as JSON.
132 #
133 # If `self` is not a valid JSON document or contains an unsupported escape
134 # sequence, return a `JSONParseError`.
135 #
136 # Example with `JsonObject`:
137 #
138 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
139 # assert obj isa JsonObject
140 # assert obj["foo"] isa JsonObject
141 # assert obj["foo"].as(JsonObject)["bar"] == true
142 #
143 # Example with `JsonArray`:
144 #
145 # var arr = "[1, 2, 3]".parse_json
146 # assert arr isa JsonArray
147 # assert arr.length == 3
148 # assert arr.first == 1
149 # assert arr.last == 3
150 #
151 # Example with `String`:
152 #
153 # var str = "\"foo, bar, baz\"".parse_json
154 # assert str isa String
155 # assert str == "foo, bar, baz"
156 #
157 # Example of a syntaxic error:
158 #
159 # var bad = "\{foo: \"bar\"\}".parse_json
160 # assert bad isa JsonParseError
161 # assert bad.position.col_start == 2
162 fun parse_json: nullable Jsonable do
163 var lexer = new Lexer_json(to_s)
164 var parser = new Parser_json
165 var tokens = lexer.lex
166 parser.tokens.add_all(tokens)
167 var root_node = parser.parse
168 if root_node isa NStart then
169 return root_node.n_0.to_nit_object
170 else if root_node isa NError then
171 return new JsonParseError(root_node.message, root_node.position)
172 else abort
173 end
174 end
175
176 redef class Buffer
177
178 # Append the JSON representation of `jsonable` to `self`.
179 #
180 # Append `"null"` for `null`.
181 private fun append_json_of(jsonable: nullable Jsonable) do
182 if jsonable isa Jsonable then
183 append jsonable.to_json
184 else
185 append "null"
186 end
187 end
188 end
189
190 redef class Int
191 super Jsonable
192
193 # Encode `self` in JSON.
194 #
195 # assert 0.to_json == "0"
196 # assert (-42).to_json == "-42"
197 redef fun to_json do return self.to_s
198 end
199
200 redef class Float
201 super Jsonable
202
203 # Encode `self` in JSON.
204 #
205 # Note: Because this method use `to_s`, it may lose precision.
206 #
207 # ~~~
208 # # Will not work as expected.
209 # # assert (-0.0).to_json == "-0.0"
210 #
211 # assert (.5).to_json == "0.5"
212 # assert (0.0).to_json == "0.0"
213 # ~~~
214 redef fun to_json do return self.to_s
215 end
216
217 redef class Bool
218 super Jsonable
219
220 # Encode `self` in JSON.
221 #
222 # assert true.to_json == "true"
223 # assert false.to_json == "false"
224 redef fun to_json do return self.to_s
225 end
226
227 # A map that can be translated into a JSON object.
228 interface JsonMapRead[K: String, V: nullable Jsonable]
229 super MapRead[K, V]
230 super Jsonable
231
232 redef fun append_json(buffer) do
233 buffer.append "\{"
234 var it = iterator
235 if it.is_ok then
236 append_json_entry(it, buffer)
237 while it.is_ok do
238 buffer.append ","
239 append_json_entry(it, buffer)
240 end
241 end
242 it.finish
243 buffer.append "\}"
244 end
245
246 # Encode `self` in JSON.
247 #
248 # var obj = new JsonObject
249 # obj["foo"] = "bar"
250 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
251 # obj = new JsonObject
252 # obj["baz"] = null
253 # assert obj.to_json == "\{\"baz\":null\}"
254 redef fun to_json do return to_json_by_append
255
256 redef fun pretty_json_visit(buffer, indent) do
257 buffer.append "\{\n"
258 indent += 1
259 var i = 0
260 for k, v in self do
261 buffer.append "\t" * indent
262 buffer.append "\"{k}\": "
263 if v isa JsonObject or v isa JsonArray then
264 v.pretty_json_visit(buffer, indent)
265 else
266 buffer.append v.to_json
267 end
268 if i < length - 1 then
269 buffer.append ","
270 end
271 buffer.append "\n"
272 i += 1
273 end
274 indent -= 1
275 buffer.append "\t" * indent
276 buffer.append "\}"
277 end
278
279 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
280 buffer: Buffer) do
281 buffer.append iterator.key.to_json
282 buffer.append ":"
283 buffer.append_json_of(iterator.item)
284 iterator.next
285 end
286 end
287
288 # A JSON Object.
289 class JsonObject
290 super JsonMapRead[String, nullable Jsonable]
291 super HashMap[String, nullable Jsonable]
292 end
293
294 # A sequence that can be translated into a JSON array.
295 class JsonSequenceRead[E: nullable Jsonable]
296 super Jsonable
297 super SequenceRead[E]
298
299 redef fun append_json(buffer) do
300 buffer.append "["
301 var it = iterator
302 if it.is_ok then
303 append_json_entry(it, buffer)
304 while it.is_ok do
305 buffer.append ","
306 append_json_entry(it, buffer)
307 end
308 end
309 it.finish
310 buffer.append "]"
311 end
312
313 # Encode `self` in JSON.
314 #
315 # var arr = new JsonArray.with_items("foo", null)
316 # assert arr.to_json == "[\"foo\",null]"
317 # arr.pop
318 # assert arr.to_json =="[\"foo\"]"
319 # arr.pop
320 # assert arr.to_json =="[]"
321 redef fun to_json do return to_json_by_append
322
323 redef fun pretty_json_visit(buffer, indent) do
324 buffer.append "\["
325 var i = 0
326 for v in self do
327 if v isa JsonObject or v isa JsonArray then
328 v.pretty_json_visit(buffer, indent)
329 else
330 buffer.append v.to_json
331 end
332 if i < length - 1 then buffer.append ", "
333 i += 1
334 end
335 buffer.append "\]"
336 end
337
338 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
339 buffer: Buffer) do
340 buffer.append_json_of(iterator.item)
341 iterator.next
342 end
343 end
344
345 # A JSON array.
346 class JsonArray
347 super JsonSequenceRead[nullable Jsonable]
348 super Array[nullable Jsonable]
349 end
350
351 redef class JsonParseError
352 super Jsonable
353
354 # Get the JSON representation of `self`.
355 #
356 # ~~~
357 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
358 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
359 # "\"position\":\{" +
360 # "\"pos_start\":1,\"pos_end\":2," +
361 # "\"line_start\":3,\"line_end\":4," +
362 # "\"col_start\":5,\"col_end\":6" +
363 # "\},\"message\":\"foo\"\}"
364 # ~~~
365 redef fun to_json do
366 return "\{\"error\":\"JsonParseError\"," +
367 "\"position\":{position.to_json}," +
368 "\"message\":{message.to_json}\}"
369 end
370 end
371
372 redef class Position
373 super Jsonable
374
375 # Get the JSON representation of `self`.
376 #
377 # ~~~
378 # var pos = new Position(1, 2, 3, 4, 5, 6)
379 # assert pos.to_json == "\{" +
380 # "\"pos_start\":1,\"pos_end\":2," +
381 # "\"line_start\":3,\"line_end\":4," +
382 # "\"col_start\":5,\"col_end\":6" +
383 # "\}"
384 # ~~~
385 redef fun to_json do
386 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
387 "\"line_start\":{line_start},\"line_end\":{line_end}," +
388 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
389 end
390 end
391
392 ################################################################################
393 # Redef parser
394
395 redef class Nvalue
396 # The represented value.
397 private fun to_nit_object: nullable Jsonable is abstract
398 end
399
400 redef class Nvalue_number
401 redef fun to_nit_object
402 do
403 var text = n_number.text
404 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
405 return text.to_i
406 end
407 end
408
409 redef class Nvalue_string
410 redef fun to_nit_object do return n_string.to_nit_string
411 end
412
413 redef class Nvalue_true
414 redef fun to_nit_object do return true
415 end
416
417 redef class Nvalue_false
418 redef fun to_nit_object do return false
419 end
420
421 redef class Nvalue_null
422 redef fun to_nit_object do return null
423 end
424
425 redef class Nstring
426 # The represented string.
427 private fun to_nit_string: String do
428 var res = new Buffer
429 var i = 1
430 while i < text.length - 1 do
431 var char = text[i]
432 if char == '\\' then
433 i += 1
434 char = text[i]
435 if char == 'b' then
436 char = 0x08.code_point
437 else if char == 'f' then
438 char = 0x0C.code_point
439 else if char == 'n' then
440 char = '\n'
441 else if char == 'r' then
442 char = '\r'
443 else if char == 't' then
444 char = '\t'
445 else if char == 'u' then
446 var escape = new Buffer
447 escape.append "\\u"
448 var code = text.substring(i + 1, 4)
449 escape.append code
450 var hx = code.to_hex
451 if hx >= 0xD800 and hx <= 0xDFFF then
452 var lostr = text.substring(i + 7, 4)
453 if lostr.length < 4 then
454 escape.clear
455 escape.append "\\uFFFD"
456 else
457 escape.append "\\u"
458 escape.append lostr
459 end
460 i += 6
461 end
462 i += 4
463 char = escape.from_utf16_escape
464 end
465 # `"`, `/` or `\` => Keep `char` as-is.
466 end
467 res.add char
468 i += 1
469 end
470 return res.write_to_string
471 end
472 end
473
474 redef class Nvalue_object
475 redef fun to_nit_object do
476 var obj = new JsonObject
477 var members = n_members
478 if members != null then
479 var pairs = members.pairs
480 for pair in pairs do obj[pair.name] = pair.value
481 end
482 return obj
483 end
484 end
485
486 redef class Nmembers
487 # All the key-value pairs.
488 private fun pairs: Array[Npair] is abstract
489 end
490
491 redef class Nmembers_tail
492 redef fun pairs
493 do
494 var arr = n_members.pairs
495 arr.add n_pair
496 return arr
497 end
498 end
499
500 redef class Nmembers_head
501 redef fun pairs do return [n_pair]
502 end
503
504 redef class Npair
505 # The represented key.
506 private fun name: String do return n_string.to_nit_string
507
508 # The represented value.
509 private fun value: nullable Jsonable do return n_value.to_nit_object
510 end
511
512 redef class Nvalue_array
513 redef fun to_nit_object
514 do
515 var arr = new JsonArray
516 var elements = n_elements
517 if elements != null then
518 var items = elements.items
519 for item in items do arr.add(item.to_nit_object)
520 end
521 return arr
522 end
523 end
524
525 redef class Nelements
526 # All the items.
527 private fun items: Array[Nvalue] is abstract
528 end
529
530 redef class Nelements_tail
531 redef fun items
532 do
533 var items = n_elements.items
534 items.add(n_value)
535 return items
536 end
537 end
538
539 redef class Nelements_head
540 redef fun items do return [n_value]
541 end