libs/core: Reworked some basic libraries to not use package.seeall
[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
16 local util      = require "luci.util"
17 local ltn12     = require "luci.ltn12"
18 local table     = require "table"
19 local coroutine = require "coroutine"
20
21 local assert    = assert
22 local tonumber  = tonumber
23 local error     = error
24
25 module "luci.json"
26
27 --- Null replacement function
28 -- @return null
29 function null()
30         return null
31 end
32
33 Decoder = util.class()
34
35 --- Create an LTN12 sink from the decoder object
36 -- @return LTN12 sink
37 function Decoder.sink(self)
38         local sink = coroutine.create(self.dispatch)
39         return function(...)
40                 return coroutine.resume(sink, self, ...)
41         end
42 end
43
44
45 --- Get the decoded data packets
46 -- @return Decoded data
47 function Decoder.get(self)
48         return self.data
49 end
50
51
52 function Decoder.dispatch(self, chunk, src_err, strict)
53         local robject, object
54          
55         while chunk do
56                 if #chunk < 1 then
57                         chunk = self:fetch()
58                 end
59                 
60                 assert(not strict or chunk, "Unexpected EOS")
61                 if not chunk then
62                         break
63                 end
64                 
65                 local parser = nil
66                 local char   = chunk:sub(1, 1)
67                 
68                 if char == '"' then
69                         parser = self.parse_string
70                 elseif char == 't' then
71                         parser = self.parse_true
72                 elseif char == 'f' then
73                         parser = self.parse_false
74                 elseif char == 'n' then
75                         parser = self.parse_null
76                 elseif char == '[' then
77                         parser = self.parse_array
78                 elseif char == '{' then
79                         parser = self.parse_object
80                 elseif char:match("%s") then
81                         parser = self.parse_space
82                 elseif char:match("[0-9-]") then
83                         parser = self.parse_number
84                 end
85                 
86                 if parser then
87                         chunk, robject = parser(self, chunk)
88                         
89                         if robject ~= nil then
90                                 assert(object == nil, "Scope violation: Too many objects")
91                                 object = robject
92                         end
93                         
94                         if strict and object ~= nil then
95                                 return chunk, object
96                         end
97                 else
98                         error("Unexpected char '%s'" % char)
99                 end
100         end
101         
102         assert(not src_err, src_err)
103         assert(object ~= nil, "Unexpected EOS")
104         
105         self.data = object
106         return chunk, object
107 end
108
109
110 function Decoder.fetch(self)
111         local tself, chunk, src_err = coroutine.yield()
112         assert(chunk or not src_err, src_err)
113         return chunk
114 end
115
116
117 function Decoder.fetch_atleast(self, chunk, bytes)
118         while #chunk < bytes do
119                 local nchunk = self:fetch()
120                 assert(nchunk, "Unexpected EOS")
121                 chunk = chunk .. nchunk
122         end
123         
124         return chunk
125 end
126
127
128 function Decoder.fetch_until(self, chunk, pattern)
129         local start = chunk:find(pattern)
130
131         while not start do
132                 local nchunk = self:fetch()
133                 assert(nchunk, "Unexpected EOS")
134                 chunk = chunk .. nchunk
135                 start = chunk:find(pattern)
136         end
137
138         return chunk, start
139 end
140
141
142 function Decoder.parse_space(self, chunk)
143         local start = chunk:find("[^%s]")
144         
145         while not start do
146                 chunk = self:fetch()
147                 if not chunk then
148                         return nil
149                 end
150                 start = chunk:find("[^%s]")
151         end
152         
153         return chunk:sub(start)
154 end
155
156
157 function Decoder.parse_literal(self, chunk, literal, value)
158         chunk = self:fetch_atleast(chunk, #literal)     
159         assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
160         return chunk:sub(#literal + 1), value
161 end
162
163
164 function Decoder.parse_null(self, chunk)
165         return self:parse_literal(chunk, "null", null)
166 end
167
168
169 function Decoder.parse_true(self, chunk)
170         return self:parse_literal(chunk, "true", true)
171 end
172
173
174 function Decoder.parse_false(self, chunk)
175         return self:parse_literal(chunk, "false", false)
176 end
177
178
179 function Decoder.parse_number(self, chunk)
180         local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
181         local number = tonumber(chunk:sub(1, start - 1))
182         assert(number, "Invalid number specification")
183         return chunk:sub(start), number
184 end
185
186
187 function Decoder.parse_string(self, chunk)
188         local str = ""
189         local object = nil
190         assert(chunk:sub(1, 1) == '"', 'Expected "')
191         chunk = chunk:sub(2)
192
193         while true do
194                 local spos = chunk:find('[\\"]')
195                 if spos then
196                         str = str .. chunk:sub(1, spos - 1)
197                         
198                         local char = chunk:sub(spos, spos)
199                         if char == '"' then                             -- String end
200                                 chunk = chunk:sub(spos + 1)
201                                 break
202                         elseif char == "\\" then                -- Escape sequence
203                                 chunk, object = self:parse_escape(chunk:sub(spos))
204                                 str = str .. object
205                         end
206                 else
207                         str = str .. chunk
208                         chunk = self:fetch()
209                         assert(chunk, "Unexpected EOS while parsing a string")          
210                 end
211         end
212
213         return chunk, str
214 end
215
216
217 function Decoder.parse_escape(self, chunk)
218         local str = ""
219         chunk = self:fetch_atleast(chunk:sub(2), 1)
220         local char = chunk:sub(1, 1)
221         chunk = chunk:sub(2)
222         
223         if char == '"' then
224                 return chunk, '"'
225         elseif char == "\\" then
226                 return chunk, "\\"
227         elseif char == "/" then
228                 return chunk, "/"
229         elseif char == "b" then
230                 return chunk, "\b"
231         elseif char == "f" then
232                 return chunk, "\f"
233         elseif char == "n" then
234                 return chunk, "\n"
235         elseif char == "r" then
236                 return chunk, "\r"
237         elseif char == "t" then
238                 return chunk, "\t"
239         elseif char == "u" then
240                 chunk = self:fetch_atleast(chunk, 4)
241                 local s1, s2 = chunk:sub(1, 4):match("^([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])$")
242                 assert(s1 and s2, "Invalid Unicode character 'U+%s%s'" % {s1, s2})
243                 s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
244                 
245                 -- ToDo: Unicode support
246                 return chunk:sub(5), s1 == 0 and s2 or ""
247         else
248                 error("Unexpected escaping sequence '\\%s'" % char)
249         end
250 end
251
252
253 function Decoder.parse_array(self, chunk)
254         chunk = chunk:sub(2)
255         local array = {}
256         
257         local chunk, object = self:parse_delimiter(chunk, "%]")
258         
259         if object then
260                 return chunk, array
261         end
262         
263         repeat
264                 chunk, object = self:dispatch(chunk, nil, true)
265                 table.insert(array, object)
266                 
267                 chunk, object = self:parse_delimiter(chunk, ",%]")
268                 assert(object, "Delimiter expected")
269         until object == "]"
270
271         return chunk, array
272 end
273
274
275 function Decoder.parse_object(self, chunk)
276         chunk = chunk:sub(2)
277         local array = {}
278         local name
279
280         local chunk, object = self:parse_delimiter(chunk, "}")
281
282         if object then
283                 return chunk, array
284         end
285
286         repeat
287                 chunk = self:parse_space(chunk)
288                 assert(chunk, "Unexpected EOS")
289                 
290                 chunk, name   = self:parse_string(chunk)
291                 
292                 chunk, object = self:parse_delimiter(chunk, ":")
293                 assert(object, "Separator expected")
294                 
295                 chunk, object = self:dispatch(chunk, nil, true)
296                 array[name] = object
297
298                 chunk, object = self:parse_delimiter(chunk, ",}")
299                 assert(object, "Delimiter expected")
300         until object == "}"
301
302         return chunk, array
303 end
304
305
306 function Decoder.parse_delimiter(self, chunk, delimiter)
307         while true do
308                 chunk = self:fetch_atleast(chunk, 1)
309                 local char = chunk:sub(1, 1)
310                 if char:match("%s") then
311                         chunk = self:parse_space(chunk)
312                         assert(chunk, "Unexpected EOS")
313                 elseif char:match("[%s]" % delimiter) then
314                         return chunk:sub(2), char
315                 else
316                         return chunk, nil
317                 end
318         end
319 end