lib/json: Improved performance of Unicode-escaping in a `Text`
[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 FlatBuffer
51 append_json(buffer)
52 return buffer.to_s
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.to_s
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 # Removes JSON-escaping if necessary in a JSON string
93 #
94 # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
95 fun unescape_json: Text do
96 if not json_need_escape then return self
97 return self.json_to_nit_string
98 end
99
100 # Does `self` need treatment from JSON to Nit ?
101 #
102 # i.e. is there at least one `\` character in it ?
103 #
104 # assert not "string".json_need_escape
105 # assert "\\\"string\\\"".json_need_escape
106 protected fun json_need_escape: Bool do return has('\\')
107
108 redef fun append_json(buffer) do
109 buffer.add '\"'
110 for i in [0 .. self.length[ do
111 var char = self[i]
112 if char == '\\' then
113 buffer.append "\\\\"
114 else if char == '\"' then
115 buffer.append "\\\""
116 else if char < ' ' then
117 if char == '\n' then
118 buffer.append "\\n"
119 else if char == '\r' then
120 buffer.append "\\r"
121 else if char == '\t' then
122 buffer.append "\\t"
123 else
124 buffer.append char.escape_to_utf16
125 end
126 else
127 buffer.add char
128 end
129 end
130 buffer.add '\"'
131 end
132
133 # Escapes `self` from a JSON string to a Nit string
134 #
135 # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
136 # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
137 # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
138 protected fun json_to_nit_string: String do
139 var res = new FlatBuffer.with_capacity(bytelen)
140 var i = 0
141 var ln = self.length
142 while i < ln do
143 var char = self[i]
144 if char == '\\' then
145 i += 1
146 char = self[i]
147 if char == 'b' then
148 char = 0x08.code_point
149 else if char == 'f' then
150 char = 0x0C.code_point
151 else if char == 'n' then
152 char = '\n'
153 else if char == 'r' then
154 char = '\r'
155 else if char == 't' then
156 char = '\t'
157 else if char == 'u' then
158 var u16_esc = from_utf16_digit(i + 1)
159 char = u16_esc.code_point
160 if char.is_surrogate and i + 10 < ln then
161 if self[i + 5] == '\\' and self[i + 6] == 'u' then
162 u16_esc <<= 16
163 u16_esc += from_utf16_digit(i + 7)
164 char = u16_esc.from_utf16_surr.code_point
165 i += 6
166 else
167 char = 0xFFFD.code_point
168 end
169 end
170 i += 4
171 end
172 # `"`, `/` or `\` => Keep `char` as-is.
173 end
174 res.add char
175 i += 1
176 end
177 return res.to_s
178 end
179
180
181 # Encode `self` in JSON.
182 #
183 # ~~~
184 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
185 # "\"\\t\\\"http://example.com\\\"\\r\\n\\u0000\\\\\""
186 # ~~~
187 redef fun to_json do
188 var b = new FlatBuffer.with_capacity(bytelen)
189 append_json(b)
190 return b.to_s
191 end
192
193 # Parse `self` as JSON.
194 #
195 # If `self` is not a valid JSON document or contains an unsupported escape
196 # sequence, return a `JSONParseError`.
197 #
198 # Example with `JsonObject`:
199 #
200 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
201 # assert obj isa JsonObject
202 # assert obj["foo"] isa JsonObject
203 # assert obj["foo"].as(JsonObject)["bar"] == true
204 #
205 # Example with `JsonArray`:
206 #
207 # var arr = "[1, 2, 3]".parse_json
208 # assert arr isa JsonArray
209 # assert arr.length == 3
210 # assert arr.first == 1
211 # assert arr.last == 3
212 #
213 # Example with `String`:
214 #
215 # var str = "\"foo, bar, baz\"".parse_json
216 # assert str isa String
217 # assert str == "foo, bar, baz"
218 #
219 # Example of a syntaxic error:
220 #
221 # var bad = "\{foo: \"bar\"\}".parse_json
222 # assert bad isa JsonParseError
223 # assert bad.position.col_start == 2
224 fun parse_json: nullable Jsonable do
225 var lexer = new Lexer_json(to_s)
226 var parser = new Parser_json
227 var tokens = lexer.lex
228 parser.tokens.add_all(tokens)
229 var root_node = parser.parse
230 if root_node isa NStart then
231 return root_node.n_0.to_nit_object
232 else if root_node isa NError then
233 return new JsonParseError(root_node.message, root_node.position)
234 else abort
235 end
236 end
237
238 redef class FlatText
239 redef fun json_need_escape do
240 var its = items
241 for i in [first_byte .. last_byte] do
242 if its[i] == 0x5Cu8 then return true
243 end
244 return false
245 end
246 end
247
248 redef class Buffer
249
250 # Append the JSON representation of `jsonable` to `self`.
251 #
252 # Append `"null"` for `null`.
253 private fun append_json_of(jsonable: nullable Jsonable) do
254 if jsonable isa Jsonable then
255 append jsonable.to_json
256 else
257 append "null"
258 end
259 end
260 end
261
262 redef class Int
263 super Jsonable
264
265 # Encode `self` in JSON.
266 #
267 # assert 0.to_json == "0"
268 # assert (-42).to_json == "-42"
269 redef fun to_json do return self.to_s
270 end
271
272 redef class Float
273 super Jsonable
274
275 # Encode `self` in JSON.
276 #
277 # Note: Because this method use `to_s`, it may lose precision.
278 #
279 # ~~~
280 # # Will not work as expected.
281 # # assert (-0.0).to_json == "-0.0"
282 #
283 # assert (.5).to_json == "0.5"
284 # assert (0.0).to_json == "0.0"
285 # ~~~
286 redef fun to_json do return self.to_s
287 end
288
289 redef class Bool
290 super Jsonable
291
292 # Encode `self` in JSON.
293 #
294 # assert true.to_json == "true"
295 # assert false.to_json == "false"
296 redef fun to_json do return self.to_s
297 end
298
299 # A map that can be translated into a JSON object.
300 interface JsonMapRead[K: String, V: nullable Jsonable]
301 super MapRead[K, V]
302 super Jsonable
303
304 redef fun append_json(buffer) do
305 buffer.append "\{"
306 var it = iterator
307 if it.is_ok then
308 append_json_entry(it, buffer)
309 while it.is_ok do
310 buffer.append ","
311 append_json_entry(it, buffer)
312 end
313 end
314 it.finish
315 buffer.append "\}"
316 end
317
318 # Encode `self` in JSON.
319 #
320 # var obj = new JsonObject
321 # obj["foo"] = "bar"
322 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
323 # obj = new JsonObject
324 # obj["baz"] = null
325 # assert obj.to_json == "\{\"baz\":null\}"
326 redef fun to_json do return to_json_by_append
327
328 redef fun pretty_json_visit(buffer, indent) do
329 buffer.append "\{\n"
330 indent += 1
331 var i = 0
332 for k, v in self do
333 buffer.append "\t" * indent
334 buffer.append "\"{k}\": "
335 if v isa JsonObject or v isa JsonArray then
336 v.pretty_json_visit(buffer, indent)
337 else
338 buffer.append v.to_json
339 end
340 if i < length - 1 then
341 buffer.append ","
342 end
343 buffer.append "\n"
344 i += 1
345 end
346 indent -= 1
347 buffer.append "\t" * indent
348 buffer.append "\}"
349 end
350
351 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
352 buffer: Buffer) do
353 buffer.append iterator.key.to_json
354 buffer.append ":"
355 buffer.append_json_of(iterator.item)
356 iterator.next
357 end
358 end
359
360 # A JSON Object.
361 class JsonObject
362 super JsonMapRead[String, nullable Jsonable]
363 super HashMap[String, nullable Jsonable]
364 end
365
366 # A sequence that can be translated into a JSON array.
367 class JsonSequenceRead[E: nullable Jsonable]
368 super Jsonable
369 super SequenceRead[E]
370
371 redef fun append_json(buffer) do
372 buffer.append "["
373 var it = iterator
374 if it.is_ok then
375 append_json_entry(it, buffer)
376 while it.is_ok do
377 buffer.append ","
378 append_json_entry(it, buffer)
379 end
380 end
381 it.finish
382 buffer.append "]"
383 end
384
385 # Encode `self` in JSON.
386 #
387 # var arr = new JsonArray.with_items("foo", null)
388 # assert arr.to_json == "[\"foo\",null]"
389 # arr.pop
390 # assert arr.to_json =="[\"foo\"]"
391 # arr.pop
392 # assert arr.to_json =="[]"
393 redef fun to_json do return to_json_by_append
394
395 redef fun pretty_json_visit(buffer, indent) do
396 buffer.append "\["
397 var i = 0
398 for v in self do
399 if v isa JsonObject or v isa JsonArray then
400 v.pretty_json_visit(buffer, indent)
401 else
402 buffer.append v.to_json
403 end
404 if i < length - 1 then buffer.append ", "
405 i += 1
406 end
407 buffer.append "\]"
408 end
409
410 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
411 buffer: Buffer) do
412 buffer.append_json_of(iterator.item)
413 iterator.next
414 end
415 end
416
417 # A JSON array.
418 class JsonArray
419 super JsonSequenceRead[nullable Jsonable]
420 super Array[nullable Jsonable]
421 end
422
423 redef class JsonParseError
424 super Jsonable
425
426 # Get the JSON representation of `self`.
427 #
428 # ~~~
429 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
430 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
431 # "\"position\":\{" +
432 # "\"pos_start\":1,\"pos_end\":2," +
433 # "\"line_start\":3,\"line_end\":4," +
434 # "\"col_start\":5,\"col_end\":6" +
435 # "\},\"message\":\"foo\"\}"
436 # ~~~
437 redef fun to_json do
438 return "\{\"error\":\"JsonParseError\"," +
439 "\"position\":{position.to_json}," +
440 "\"message\":{message.to_json}\}"
441 end
442 end
443
444 redef class Position
445 super Jsonable
446
447 # Get the JSON representation of `self`.
448 #
449 # ~~~
450 # var pos = new Position(1, 2, 3, 4, 5, 6)
451 # assert pos.to_json == "\{" +
452 # "\"pos_start\":1,\"pos_end\":2," +
453 # "\"line_start\":3,\"line_end\":4," +
454 # "\"col_start\":5,\"col_end\":6" +
455 # "\}"
456 # ~~~
457 redef fun to_json do
458 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
459 "\"line_start\":{line_start},\"line_end\":{line_end}," +
460 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
461 end
462 end
463
464 ################################################################################
465 # Redef parser
466
467 redef class Nvalue
468 # The represented value.
469 private fun to_nit_object: nullable Jsonable is abstract
470 end
471
472 redef class Nvalue_number
473 redef fun to_nit_object
474 do
475 var text = n_number.text
476 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
477 return text.to_i
478 end
479 end
480
481 redef class Nvalue_string
482 redef fun to_nit_object do return n_string.to_nit_string
483 end
484
485 redef class Nvalue_true
486 redef fun to_nit_object do return true
487 end
488
489 redef class Nvalue_false
490 redef fun to_nit_object do return false
491 end
492
493 redef class Nvalue_null
494 redef fun to_nit_object do return null
495 end
496
497 redef class Nstring
498 # The represented string.
499 private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
500 end
501
502 redef class Nvalue_object
503 redef fun to_nit_object do
504 var obj = new JsonObject
505 var members = n_members
506 if members != null then
507 var pairs = members.pairs
508 for pair in pairs do obj[pair.name] = pair.value
509 end
510 return obj
511 end
512 end
513
514 redef class Nmembers
515 # All the key-value pairs.
516 private fun pairs: Array[Npair] is abstract
517 end
518
519 redef class Nmembers_tail
520 redef fun pairs
521 do
522 var arr = n_members.pairs
523 arr.add n_pair
524 return arr
525 end
526 end
527
528 redef class Nmembers_head
529 redef fun pairs do return [n_pair]
530 end
531
532 redef class Npair
533 # The represented key.
534 private fun name: String do return n_string.to_nit_string
535
536 # The represented value.
537 private fun value: nullable Jsonable do return n_value.to_nit_object
538 end
539
540 redef class Nvalue_array
541 redef fun to_nit_object
542 do
543 var arr = new JsonArray
544 var elements = n_elements
545 if elements != null then
546 var items = elements.items
547 for item in items do arr.add(item.to_nit_object)
548 end
549 return arr
550 end
551 end
552
553 redef class Nelements
554 # All the items.
555 private fun items: Array[Nvalue] is abstract
556 end
557
558 redef class Nelements_tail
559 redef fun items
560 do
561 var items = n_elements.items
562 items.add(n_value)
563 return items
564 end
565 end
566
567 redef class Nelements_head
568 redef fun items do return [n_value]
569 end