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