bb578fdd89152992b2809b89555341c936d9d3b5
[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 # Append the JSON representation of `self` to the specified buffer.
38 #
39 # SEE: `to_json`
40 fun append_json(buffer: Buffer) do
41 buffer.append(to_json)
42 end
43 end
44
45 redef class Text
46 super Jsonable
47
48 redef fun append_json(buffer) do
49 buffer.add '\"'
50 for i in [0..self.length[ do
51 var char = self[i]
52 if char == '\\' then
53 buffer.append "\\\\"
54 else if char == '\"' then
55 buffer.append "\\\""
56 else if char == '\/' then
57 buffer.append "\\/"
58 else if char < 16.ascii then
59 if char == '\n' then
60 buffer.append "\\n"
61 else if char == '\r' then
62 buffer.append "\\r"
63 else if char == '\t' then
64 buffer.append "\\t"
65 else if char == 0x0C.ascii then
66 buffer.append "\\f"
67 else if char == 0x08.ascii then
68 buffer.append "\\b"
69 else
70 buffer.append "\\u000{char.ascii.to_hex}"
71 end
72 else if char < ' ' then
73 buffer.append "\\u00{char.ascii.to_hex}"
74 else
75 buffer.add char
76 end
77 end
78 buffer.add '\"'
79 end
80
81 # Encode `self` in JSON.
82 #
83 # assert "\t\"http://example.com\"\r\n\0\\".to_json ==
84 # "\"\\t\\\"http:\\/\\/example.com\\\"\\r\\n\\u0000\\\\\""
85 redef fun to_json do
86 var buffer = new FlatBuffer
87 append_json(buffer)
88 return buffer.write_to_string
89 end
90
91 # Parse `self` as JSON.
92 #
93 # If `self` is not a valid JSON document or contains an unsupported escape
94 # sequence, return a `JSONParseError`.
95 #
96 # Example with `JsonObject`:
97 #
98 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
99 # assert obj isa JsonObject
100 # assert obj["foo"] isa JsonObject
101 # assert obj["foo"].as(JsonObject)["bar"] == true
102 #
103 # Example with `JsonArray`:
104 #
105 # var arr = "[1, 2, 3]".parse_json
106 # assert arr isa JsonArray
107 # assert arr.length == 3
108 # assert arr.first == 1
109 # assert arr.last == 3
110 #
111 # Example with `String`:
112 #
113 # var str = "\"foo, bar, baz\"".parse_json
114 # assert str isa String
115 # assert str == "foo, bar, baz"
116 #
117 # Example of a syntaxic error:
118 #
119 # var bad = "\{foo: \"bar\"\}".parse_json
120 # assert bad isa JsonParseError
121 # assert bad.position.col_start == 2
122 fun parse_json: nullable Jsonable do
123 var lexer = new Lexer_json(to_s)
124 var parser = new Parser_json
125 var tokens = lexer.lex
126 parser.tokens.add_all(tokens)
127 var root_node = parser.parse
128 if root_node isa NStart then
129 return root_node.n_0.to_nit_object
130 else if root_node isa NError then
131 return new JsonParseError(root_node.message, root_node.position)
132 else abort
133 end
134 end
135
136 redef class Buffer
137
138 # Append the JSON representation of `jsonable` to `self`.
139 #
140 # Append `"null"` for `null`.
141 private fun append_json_of(jsonable: nullable Jsonable) do
142 if jsonable isa Jsonable then
143 append jsonable.to_json
144 else
145 append "null"
146 end
147 end
148 end
149
150 redef class Int
151 super Jsonable
152
153 # Encode `self` in JSON.
154 #
155 # assert 0.to_json == "0"
156 # assert (-42).to_json == "-42"
157 redef fun to_json do return self.to_s
158 end
159
160 redef class Float
161 super Jsonable
162
163 # Encode `self` in JSON.
164 #
165 # Note: Because this method use `to_s`, it may lose precision.
166 #
167 # ~~~
168 # # Will not work as expected.
169 # # assert (-0.0).to_json == "-0.0"
170 #
171 # assert (.5).to_json == "0.5"
172 # assert (0.0).to_json == "0.0"
173 # ~~~
174 redef fun to_json do return self.to_s
175 end
176
177 redef class Bool
178 super Jsonable
179
180 # Encode `self` in JSON.
181 #
182 # assert true.to_json == "true"
183 # assert false.to_json == "false"
184 redef fun to_json do return self.to_s
185 end
186
187 # A map that can be translated into a JSON object.
188 interface JsonMapRead[K: String, V: nullable Jsonable]
189 super MapRead[K, V]
190 super Jsonable
191
192 redef fun append_json(buffer) do
193 buffer.append "\{"
194 var it = iterator
195 if it.is_ok then
196 append_json_entry(it, buffer)
197 while it.is_ok do
198 buffer.append ","
199 append_json_entry(it, buffer)
200 end
201 end
202 it.finish
203 buffer.append "\}"
204 end
205
206 # Encode `self` in JSON.
207 #
208 # var obj = new JsonObject
209 # obj["foo"] = "bar"
210 # assert obj.to_json == "\{\"foo\":\"bar\"\}"
211 # obj = new JsonObject
212 # obj["baz"] = null
213 # assert obj.to_json == "\{\"baz\":null\}"
214 redef fun to_json do
215 var buffer = new FlatBuffer
216 append_json(buffer)
217 return buffer.write_to_string
218 end
219
220 private fun append_json_entry(iterator: MapIterator[String, nullable Jsonable],
221 buffer: Buffer) do
222 buffer.append iterator.key.to_json
223 buffer.append ":"
224 buffer.append_json_of(iterator.item)
225 iterator.next
226 end
227 end
228
229 # A JSON Object.
230 class JsonObject
231 super JsonMapRead[String, nullable Jsonable]
232 super HashMap[String, nullable Jsonable]
233 end
234
235 # A sequence that can be translated into a JSON array.
236 class JsonSequenceRead[E: nullable Jsonable]
237 super Jsonable
238 super SequenceRead[E]
239
240 redef fun append_json(buffer) do
241 buffer.append "["
242 var it = iterator
243 if it.is_ok then
244 append_json_entry(it, buffer)
245 while it.is_ok do
246 buffer.append ","
247 append_json_entry(it, buffer)
248 end
249 end
250 it.finish
251 buffer.append "]"
252 end
253
254 # Encode `self` in JSON.
255 #
256 # var arr = new JsonArray.with_items("foo", null)
257 # assert arr.to_json == "[\"foo\",null]"
258 # arr.pop
259 # assert arr.to_json =="[\"foo\"]"
260 # arr.pop
261 # assert arr.to_json =="[]"
262 redef fun to_json do
263 var buffer = new FlatBuffer
264 append_json(buffer)
265 return buffer.write_to_string
266 end
267
268 private fun append_json_entry(iterator: Iterator[nullable Jsonable],
269 buffer: Buffer) do
270 buffer.append_json_of(iterator.item)
271 iterator.next
272 end
273 end
274
275 # A JSON array.
276 class JsonArray
277 super JsonSequenceRead[nullable Jsonable]
278 super Array[nullable Jsonable]
279 end
280
281 redef class JsonParseError
282 super Jsonable
283
284 # Get the JSON representation of `self`.
285 #
286 # var err = new JsonParseError("foo", new Position(1, 2, 3, 4, 5, 6))
287 # assert err.to_json == "\{\"error\":\"JsonParseError\"," +
288 # "\"position\":\{" +
289 # "\"pos_start\":1,\"pos_end\":2," +
290 # "\"line_start\":3,\"line_end\":4," +
291 # "\"col_start\":5,\"col_end\":6" +
292 # "\},\"message\":\"foo\"\}"
293 redef fun to_json do
294 return "\{\"error\":\"JsonParseError\"," +
295 "\"position\":{position.to_json}," +
296 "\"message\":{message.to_json}\}"
297 end
298 end
299
300 redef class Position
301 super Jsonable
302
303 # Get the JSON representation of `self`.
304 #
305 # var pos = new Position(1, 2, 3, 4, 5, 6)
306 # assert pos.to_json == "\{" +
307 # "\"pos_start\":1,\"pos_end\":2," +
308 # "\"line_start\":3,\"line_end\":4," +
309 # "\"col_start\":5,\"col_end\":6" +
310 # "\}"
311 redef fun to_json do
312 return "\{\"pos_start\":{pos_start},\"pos_end\":{pos_end}," +
313 "\"line_start\":{line_start},\"line_end\":{line_end}," +
314 "\"col_start\":{col_start},\"col_end\":{col_end}\}"
315 end
316 end
317
318 ################################################################################
319 # Redef parser
320
321 redef class Nvalue
322 # The represented value.
323 private fun to_nit_object: nullable Jsonable is abstract
324 end
325
326 redef class Nvalue_number
327 redef fun to_nit_object
328 do
329 var text = n_number.text
330 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
331 return text.to_i
332 end
333 end
334
335 redef class Nvalue_string
336 redef fun to_nit_object do return n_string.to_nit_string
337 end
338
339 redef class Nvalue_true
340 redef fun to_nit_object do return true
341 end
342
343 redef class Nvalue_false
344 redef fun to_nit_object do return false
345 end
346
347 redef class Nvalue_null
348 redef fun to_nit_object do return null
349 end
350
351 redef class Nstring
352 # The represented string.
353 private fun to_nit_string: String do
354 var res = new FlatBuffer
355 var i = 1
356 while i < text.length - 1 do
357 var char = text[i]
358 if char == '\\' then
359 i += 1
360 char = text[i]
361 if char == 'b' then
362 char = 0x08.ascii
363 else if char == 'f' then
364 char = 0x0C.ascii
365 else if char == 'n' then
366 char = '\n'
367 else if char == 'r' then
368 char = '\r'
369 else if char == 't' then
370 char = '\t'
371 else if char == 'u' then
372 var code = text.substring(i + 1, 4).to_hex
373 # TODO UTF-16 escaping is not supported yet.
374 if code >= 128 then
375 char = '?'
376 else
377 char = code.ascii
378 end
379 i += 4
380 end
381 # `"`, `/` or `\` => Keep `char` as-is.
382 end
383 res.add char
384 i += 1
385 end
386 return res.write_to_string
387 end
388 end
389
390 redef class Nvalue_object
391 redef fun to_nit_object do
392 var obj = new JsonObject
393 var members = n_members
394 if members != null then
395 var pairs = members.pairs
396 for pair in pairs do obj[pair.name] = pair.value
397 end
398 return obj
399 end
400 end
401
402 redef class Nmembers
403 # All the key-value pairs.
404 private fun pairs: Array[Npair] is abstract
405 end
406
407 redef class Nmembers_tail
408 redef fun pairs
409 do
410 var arr = n_members.pairs
411 arr.add n_pair
412 return arr
413 end
414 end
415
416 redef class Nmembers_head
417 redef fun pairs do return [n_pair]
418 end
419
420 redef class Npair
421 # The represented key.
422 private fun name: String do return n_string.to_nit_string
423
424 # The represented value.
425 private fun value: nullable Jsonable do return n_value.to_nit_object
426 end
427
428 redef class Nvalue_array
429 redef fun to_nit_object
430 do
431 var arr = new JsonArray
432 var elements = n_elements
433 if elements != null then
434 var items = elements.items
435 for item in items do arr.add(item.to_nit_object)
436 end
437 return arr
438 end
439 end
440
441 redef class Nelements
442 # All the items.
443 private fun items: Array[Nvalue] is abstract
444 end
445
446 redef class Nelements_tail
447 redef fun items
448 do
449 var items = n_elements.items
450 items.add(n_value)
451 return items
452 end
453 end
454
455 redef class Nelements_head
456 redef fun items do return [n_value]
457 end