text: use UInt32 to manipulate chars
[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 read Nit objects from JSON strings
20 #
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`.
23 module static
24
25 import error
26 private import json_parser
27 private import json_lexer
28
29 # Something that can be translated to JSON.
30 interface Jsonable
31 super Serializable
32 end
33
34 redef class Text
35 super Jsonable
36
37 # Removes JSON-escaping if necessary in a JSON string
38 #
39 # assert "\\\"string\\uD83D\\uDE02\\\"".unescape_json == "\"string😂\""
40 fun unescape_json: Text do
41 if not json_need_escape then return self
42 return self.json_to_nit_string
43 end
44
45 # Does `self` need treatment from JSON to Nit ?
46 #
47 # i.e. is there at least one `\` character in it ?
48 #
49 # assert not "string".json_need_escape
50 # assert "\\\"string\\\"".json_need_escape
51 protected fun json_need_escape: Bool do return has('\\')
52
53 # Escapes `self` from a JSON string to a Nit string
54 #
55 # assert "\\\"string\\\"".json_to_nit_string == "\"string\""
56 # assert "\\nEscape\\t\\n".json_to_nit_string == "\nEscape\t\n"
57 # assert "\\u0041zu\\uD800\\uDFD3".json_to_nit_string == "Azu𐏓"
58 protected fun json_to_nit_string: String do
59 var res = new FlatBuffer.with_capacity(byte_length)
60 var i = 0
61 var ln = self.length
62 while i < ln do
63 var char = self[i]
64 if char == '\\' then
65 i += 1
66 char = self[i]
67 if char == 'b' then
68 char = 0x08.code_point
69 else if char == 'f' then
70 char = 0x0C.code_point
71 else if char == 'n' then
72 char = '\n'
73 else if char == 'r' then
74 char = '\r'
75 else if char == 't' then
76 char = '\t'
77 else if char == 'u' then
78 var u16_esc = from_utf16_digit(i + 1)
79 char = u16_esc.code_point
80 if char.is_surrogate and i + 10 < ln then
81 if self[i + 5] == '\\' and self[i + 6] == 'u' then
82 u16_esc <<= 16
83 u16_esc += from_utf16_digit(i + 7)
84 char = u16_esc.to_u32.from_utf16_surr.code_point
85 i += 6
86 else
87 char = 0xFFFD.code_point
88 end
89 end
90 i += 4
91 end
92 # `"`, `/` or `\` => Keep `char` as-is.
93 end
94 res.add char
95 i += 1
96 end
97 return res.to_s
98 end
99
100 # Parse `self` as JSON.
101 #
102 # If `self` is not a valid JSON document or contains an unsupported escape
103 # sequence, return a `JSONParseError`.
104 #
105 # Example with `JsonObject`:
106 #
107 # var obj = "\{\"foo\": \{\"bar\": true, \"goo\": [1, 2, 3]\}\}".parse_json
108 # assert obj isa JsonObject
109 # assert obj["foo"] isa JsonObject
110 # assert obj["foo"].as(JsonObject)["bar"] == true
111 #
112 # Example with `JsonArray`:
113 #
114 # var arr = "[1, 2, 3]".parse_json
115 # assert arr isa JsonArray
116 # assert arr.length == 3
117 # assert arr.first == 1
118 # assert arr.last == 3
119 #
120 # Example with `String`:
121 #
122 # var str = "\"foo, bar, baz\"".parse_json
123 # assert str isa String
124 # assert str == "foo, bar, baz"
125 #
126 # Example of a syntaxic error:
127 #
128 # var bad = "\{foo: \"bar\"\}".parse_json
129 # assert bad isa JsonParseError
130 # assert bad.position.col_start == 2
131 fun parse_json: nullable Jsonable do
132 var lexer = new Lexer_json(to_s)
133 var parser = new Parser_json
134 var tokens = lexer.lex
135 parser.tokens.add_all(tokens)
136 var root_node = parser.parse
137 if root_node isa NStart then
138 return root_node.n_0.to_nit_object
139 else if root_node isa NError then
140 return new JsonParseError(root_node.message, root_node.position)
141 else abort
142 end
143 end
144
145 redef class FlatText
146 redef fun json_need_escape do
147 var its = items
148 for i in [first_byte .. last_byte] do
149 if its[i] == 0x5Cu8 then return true
150 end
151 return false
152 end
153 end
154
155 redef class Int
156 super Jsonable
157 end
158
159 redef class Float
160 super Jsonable
161 end
162
163 redef class Bool
164 super Jsonable
165 end
166
167 # A map that can be translated into a JSON object.
168 interface JsonMapRead[K: String, V: nullable Jsonable]
169 super MapRead[K, V]
170 super Jsonable
171 end
172
173 # A JSON Object.
174 class JsonObject
175 super JsonMapRead[String, nullable Jsonable]
176 super HashMap[String, nullable Jsonable]
177 end
178
179 # A sequence that can be translated into a JSON array.
180 class JsonSequenceRead[E: nullable Jsonable]
181 super Jsonable
182 super SequenceRead[E]
183 end
184
185 # A JSON array.
186 class JsonArray
187 super JsonSequenceRead[nullable Jsonable]
188 super Array[nullable Jsonable]
189 end
190
191 redef class JsonParseError
192 super Jsonable
193 end
194
195 redef class Position
196 super Jsonable
197 end
198
199 ################################################################################
200 # Redef parser
201
202 redef class Nvalue
203 # The represented value.
204 private fun to_nit_object: nullable Jsonable is abstract
205 end
206
207 redef class Nvalue_number
208 redef fun to_nit_object
209 do
210 var text = n_number.text
211 if text.chars.has('.') or text.chars.has('e') or text.chars.has('E') then return text.to_f
212 return text.to_i
213 end
214 end
215
216 redef class Nvalue_string
217 redef fun to_nit_object do return n_string.to_nit_string
218 end
219
220 redef class Nvalue_true
221 redef fun to_nit_object do return true
222 end
223
224 redef class Nvalue_false
225 redef fun to_nit_object do return false
226 end
227
228 redef class Nvalue_null
229 redef fun to_nit_object do return null
230 end
231
232 redef class Nstring
233 # The represented string.
234 private fun to_nit_string: String do return text.substring(1, text.length - 2).unescape_json.to_s
235 end
236
237 redef class Nvalue_object
238 redef fun to_nit_object do
239 var obj = new JsonObject
240 var members = n_members
241 if members != null then
242 var pairs = members.pairs
243 for pair in pairs do obj[pair.name] = pair.value
244 end
245 return obj
246 end
247 end
248
249 redef class Nmembers
250 # All the key-value pairs.
251 private fun pairs: Array[Npair] is abstract
252 end
253
254 redef class Nmembers_tail
255 redef fun pairs
256 do
257 var arr = n_members.pairs
258 arr.add n_pair
259 return arr
260 end
261 end
262
263 redef class Nmembers_head
264 redef fun pairs do return [n_pair]
265 end
266
267 redef class Npair
268 # The represented key.
269 private fun name: String do return n_string.to_nit_string
270
271 # The represented value.
272 private fun value: nullable Jsonable do return n_value.to_nit_object
273 end
274
275 redef class Nvalue_array
276 redef fun to_nit_object
277 do
278 var arr = new JsonArray
279 var elements = n_elements
280 if elements != null then
281 var items = elements.items
282 for item in items do arr.add(item.to_nit_object)
283 end
284 return arr
285 end
286 end
287
288 redef class Nelements
289 # All the items.
290 private fun items: Array[Nvalue] is abstract
291 end
292
293 redef class Nelements_tail
294 redef fun items
295 do
296 var items = n_elements.items
297 items.add(n_value)
298 return items
299 end
300 end
301
302 redef class Nelements_head
303 redef fun items do return [n_value]
304 end