libs/json: Completed JSON library
[project/luci.git] / libs / json / luasrc / json.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
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 $Id$
14
15 Decoder:
16         Info:
17                 null will be decoded to luci.json.null if first parameter of Decoder() is true
18         
19         Example:
20                 decoder = luci.json.Decoder()
21                 luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink())
22                 luci.util.dumptable(decoder:get())
23                 
24         Known issues:
25                 does not support unicode conversion \uXXYY with XX != 00 will be ignored
26                 
27                         
28 Encoder:
29         Info:
30                 Accepts numbers, strings, nil, booleans as they are
31                 Accepts luci.json.null as replacement for nil
32                 Accepts full associative and full numerically indexed tables
33                 Mixed tables will loose their associative values during conversion
34                 Iterator functions will be encoded as an array of their return values
35                 Non-iterator functions will probably corrupt the encoder
36         
37         Example:
38                 encoder = luci.json.Encoder(encodableData)
39                 luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w)))
40 ]]--
41
42 local util      = require "luci.util"
43 local table     = require "table"
44 local string    = require "string"
45 local coroutine = require "coroutine"
46
47 local assert    = assert
48 local tonumber  = tonumber
49 local tostring  = tostring
50 local error     = error
51 local type          = type
52 local pairs         = pairs
53 local ipairs    = ipairs
54 local next      = next
55
56 local getmetatable = getmetatable
57
58 module "luci.json"
59
60 --- Null replacement function
61 -- @return null
62 function null()
63         return null
64 end
65
66
67 Encoder = util.class()
68
69 --- Creates a new Encoder.
70 -- @param data                  Data to be encoded.
71 -- @param buffersize    Buffersize of returned data.
72 -- @param fastescape    Use non-standard escaping (don't escape control chars) 
73 function Encoder.__init__(self, data, buffersize, fastescape)
74         self.data = data
75         self.buffersize = buffersize or 512
76         self.buffer = ""
77         self.fastescape = fastescape
78         
79         getmetatable(self).__call = Encoder.source
80 end
81
82 --- Create an LTN12 source from the encoder object
83 -- @return LTN12 source
84 function Encoder.source(self)
85         local source = coroutine.create(self.dispatch)
86         return function()
87                 local res, data = coroutine.resume(source, self, self.data, true)
88                 if res then
89                         return data
90                 else
91                         return nil, data
92                 end
93         end     
94 end
95
96 function Encoder.dispatch(self, data, start)
97         local parser = self.parsers[type(data)]
98         
99         parser(self, data)
100         
101         if start then
102                 if #self.buffer > 0 then
103                         coroutine.yield(self.buffer)
104                 end
105                 
106                 coroutine.yield()
107         end
108 end
109
110 function Encoder.put(self, chunk)
111         if self.buffersize < 2 then
112                 corountine.yield(chunk)
113         else
114                 if #self.buffer + #chunk > self.buffersize then
115                         local written = 0
116                         local fbuffer = self.buffersize - #self.buffer
117
118                         coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
119                         written = fbuffer
120                         
121                         while #chunk - written > self.buffersize do
122                                 fbuffer = written + self.buffersize
123                                 coroutine.yield(chunk:sub(written + 1, fbuffer))
124                                 written = fbuffer
125                         end 
126                         
127                         self.buffer = chunk:sub(written + 1)
128                 else
129                         self.buffer = self.buffer .. chunk
130                 end
131         end
132 end
133
134 function Encoder.parse_nil(self)
135         self:put("null")
136 end
137
138 function Encoder.parse_bool(self, obj)
139         self:put(obj and "true" or "false")
140 end
141
142 function Encoder.parse_number(self, obj)
143         self:put(tostring(obj))
144 end
145
146 function Encoder.parse_string(self, obj)
147         if self.fastescape then
148                 self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
149         else
150                 self:put('"' ..
151                         obj:gsub('[%c\\"]',
152                                 function(char)
153                                         return '\\u00%02x' % char:byte()
154                                 end
155                         )
156                 .. '"')
157         end
158 end
159
160 function Encoder.parse_iter(self, obj)
161         if obj == null then
162                 return self:put("null")
163         end
164
165         if type(obj) == "table" and (#obj == 0 and next(obj)) then
166                 self:put("{")
167                 local first = true
168                 
169                 for key, entry in pairs(obj) do
170                         first = first or self:put(",")
171                         first = first and false
172                         self:parse_string(tostring(key))
173                         self:put(":")
174                         self:dispatch(entry)
175                 end
176                 
177                 self:put("}")           
178         else
179                 self:put("[")
180                 local first = true
181                 
182                 if type(obj) == "table" then
183                         for i, entry in pairs(obj) do
184                                 first = first or self:put(",")
185                                 first = first and nil
186                                 self:dispatch(entry)
187                         end
188                 else
189                         for entry in obj do
190                                 first = first or self:put(",")
191                                 first = first and nil
192                                 self:dispatch(entry)
193                         end             
194                 end
195                 
196                 self:put("]")   
197         end
198 end
199
200 Encoder.parsers = {
201         ['nil']      = Encoder.parse_nil,
202         ['table']    = Encoder.parse_iter,
203         ['number']   = Encoder.parse_number,
204         ['string']   = Encoder.parse_string,
205         ['boolean']  = Encoder.parse_bool,
206         ['function'] = Encoder.parse_iter
207
208
209
210
211 Decoder = util.class()
212
213 --- Create a new Decoder object.
214 -- @param customnull User luci.json.null instead of nil
215 function Decoder.__init__(self, customnull)
216         self.cnull = customnull
217         getmetatable(self).__call = Decoder.sink
218 end
219
220 --- Create an LTN12 sink from the decoder object.
221 -- @return LTN12 sink
222 function Decoder.sink(self)
223         local sink = coroutine.create(self.dispatch)
224         return function(...)
225                 return coroutine.resume(sink, self, ...)
226         end
227 end
228
229
230 --- Get the decoded data packets
231 -- @return Decoded data
232 function Decoder.get(self)
233         return self.data
234 end
235
236 function Decoder.dispatch(self, chunk, src_err, strict)
237         local robject, object
238         local oset = false
239          
240         while chunk do
241                 while chunk and #chunk < 1 do
242                         chunk = self:fetch()
243                 end
244                 
245                 assert(not strict or chunk, "Unexpected EOS")
246                 if not chunk then break end
247                 
248                 local char   = chunk:sub(1, 1)
249                 local parser = self.parsers[char]
250                  or (char:match("%s")     and self.parse_space)
251                  or (char:match("[0-9-]") and self.parse_number)
252                  or error("Unexpected char '%s'" % char)
253                 
254                 chunk, robject = parser(self, chunk)
255                 
256                 if parser ~= self.parse_space then
257                         assert(not oset, "Scope violation: Too many objects")
258                         object = robject
259                         oset = true
260                 
261                         if strict then
262                                 return chunk, object
263                         end
264                 end
265         end
266         
267         assert(not src_err, src_err)
268         assert(oset, "Unexpected EOS")
269         
270         self.data = object
271 end
272
273
274 function Decoder.fetch(self)
275         local tself, chunk, src_err = coroutine.yield()
276         assert(chunk or not src_err, src_err)
277         return chunk
278 end
279
280
281 function Decoder.fetch_atleast(self, chunk, bytes)
282         while #chunk < bytes do
283                 local nchunk = self:fetch()
284                 assert(nchunk, "Unexpected EOS")
285                 chunk = chunk .. nchunk
286         end
287         
288         return chunk
289 end
290
291
292 function Decoder.fetch_until(self, chunk, pattern)
293         local start = chunk:find(pattern)
294
295         while not start do
296                 local nchunk = self:fetch()
297                 assert(nchunk, "Unexpected EOS")
298                 chunk = chunk .. nchunk
299                 start = chunk:find(pattern)
300         end
301
302         return chunk, start
303 end
304
305
306 function Decoder.parse_space(self, chunk)
307         local start = chunk:find("[^%s]")
308         
309         while not start do
310                 chunk = self:fetch()
311                 if not chunk then
312                         return nil
313                 end
314                 start = chunk:find("[^%s]")
315         end
316         
317         return chunk:sub(start)
318 end
319
320
321 function Decoder.parse_literal(self, chunk, literal, value)
322         chunk = self:fetch_atleast(chunk, #literal)     
323         assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
324         return chunk:sub(#literal + 1), value
325 end
326
327
328 function Decoder.parse_null(self, chunk)
329         return self:parse_literal(chunk, "null", self.cnull and null)
330 end
331
332
333 function Decoder.parse_true(self, chunk)
334         return self:parse_literal(chunk, "true", true)
335 end
336
337
338 function Decoder.parse_false(self, chunk)
339         return self:parse_literal(chunk, "false", false)
340 end
341
342
343 function Decoder.parse_number(self, chunk)
344         local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
345         local number = tonumber(chunk:sub(1, start - 1))
346         assert(number, "Invalid number specification")
347         return chunk:sub(start), number
348 end
349
350
351 function Decoder.parse_string(self, chunk)
352         local str = ""
353         local object = nil
354         assert(chunk:sub(1, 1) == '"', 'Expected "')
355         chunk = chunk:sub(2)
356
357         while true do
358                 local spos = chunk:find('[\\"]')
359                 if spos then
360                         str = str .. chunk:sub(1, spos - 1)
361                         
362                         local char = chunk:sub(spos, spos)
363                         if char == '"' then                             -- String end
364                                 chunk = chunk:sub(spos + 1)
365                                 break
366                         elseif char == "\\" then                -- Escape sequence
367                                 chunk, object = self:parse_escape(chunk:sub(spos))
368                                 str = str .. object
369                         end
370                 else
371                         str = str .. chunk
372                         chunk = self:fetch()
373                         assert(chunk, "Unexpected EOS while parsing a string")          
374                 end
375         end
376
377         return chunk, str
378 end
379
380
381 function Decoder.parse_escape(self, chunk)
382         local str = ""
383         chunk = self:fetch_atleast(chunk:sub(2), 1)
384         local char = chunk:sub(1, 1)
385         chunk = chunk:sub(2)
386         
387         if char == '"' then
388                 return chunk, '"'
389         elseif char == "\\" then
390                 return chunk, "\\"
391         elseif char == "u" then
392                 chunk = self:fetch_atleast(chunk, 4)
393                 local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
394                 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
395                 assert(s1 and s2, "Invalid Unicode character")
396                 
397                 -- ToDo: Unicode support
398                 return chunk:sub(5), s1 == 0 and string.char(s2) or ""
399         elseif char == "/" then
400                 return chunk, "/"
401         elseif char == "b" then
402                 return chunk, "\b"
403         elseif char == "f" then
404                 return chunk, "\f"
405         elseif char == "n" then
406                 return chunk, "\n"
407         elseif char == "r" then
408                 return chunk, "\r"
409         elseif char == "t" then
410                 return chunk, "\t"
411         else
412                 error("Unexpected escaping sequence '\\%s'" % char)
413         end
414 end
415
416
417 function Decoder.parse_array(self, chunk)
418         chunk = chunk:sub(2)
419         local array = {}
420         local nextp = 1
421         
422         local chunk, object = self:parse_delimiter(chunk, "%]")
423         
424         if object then
425                 return chunk, array
426         end
427         
428         repeat
429                 chunk, object = self:dispatch(chunk, nil, true)
430                 table.insert(array, nextp, object)
431                 nextp = nextp + 1
432                 
433                 chunk, object = self:parse_delimiter(chunk, ",%]")
434                 assert(object, "Delimiter expected")
435         until object == "]"
436
437         return chunk, array
438 end
439
440
441 function Decoder.parse_object(self, chunk)
442         chunk = chunk:sub(2)
443         local array = {}
444         local name
445
446         local chunk, object = self:parse_delimiter(chunk, "}")
447
448         if object then
449                 return chunk, array
450         end
451
452         repeat
453                 chunk = self:parse_space(chunk)
454                 assert(chunk, "Unexpected EOS")
455                 
456                 chunk, name   = self:parse_string(chunk)
457                 
458                 chunk, object = self:parse_delimiter(chunk, ":")
459                 assert(object, "Separator expected")
460                 
461                 chunk, object = self:dispatch(chunk, nil, true)
462                 array[name] = object
463
464                 chunk, object = self:parse_delimiter(chunk, ",}")
465                 assert(object, "Delimiter expected")
466         until object == "}"
467
468         return chunk, array
469 end
470
471
472 function Decoder.parse_delimiter(self, chunk, delimiter)
473         while true do
474                 chunk = self:fetch_atleast(chunk, 1)
475                 local char = chunk:sub(1, 1)
476                 if char:match("%s") then
477                         chunk = self:parse_space(chunk)
478                         assert(chunk, "Unexpected EOS")
479                 elseif char:match("[%s]" % delimiter) then
480                         return chunk:sub(2), char
481                 else
482                         return chunk, nil
483                 end
484         end
485 end
486
487
488 Decoder.parsers = { 
489         ['"'] = Decoder.parse_string,
490         ['t'] = Decoder.parse_true,
491         ['f'] = Decoder.parse_false,
492         ['n'] = Decoder.parse_null,
493         ['['] = Decoder.parse_array,
494         ['{'] = Decoder.parse_object
495 }