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