]> Pileus Git - ~andy/rhawk/blob - json.awk
Add line numbers to json error messages
[~andy/rhawk] / json.awk
1 # value:
2 #       object: { string : value, .. }
3 #       array:  [ value, .. ]
4 #       string: "char .."
5 #       number: (builtin)
6 #       true
7 #       false
8 #       null
9
10 # chars:
11 #       any-Unicode-character-except-"-or-\-or-control-character
12 #       \"
13 #       \\
14 #       \/
15 #       \b
16 #       \f
17 #       \n
18 #       \r
19 #       \t
20 #       \u four-hex-digits
21
22 # Helpers
23 function json_gettype(value,   key, sort, n, i)
24 {
25         if (isarray(value)) { 
26                 for (key in value)
27                         if (isarray(key))
28                                 return "error"
29                 n = asorti(value, sort)
30                 for (i = 0; i < n; i++)
31                         if (sort[i+1] != i)
32                                 return "object"
33                 return "array"
34         } else {
35                 if (value == 0 && value == "")
36                         return "null"
37                 if (value == value + 0)
38                         return "number"
39                 if (value == value "")
40                         return "string"
41                 return "error"
42         }
43 }
44
45 function json_join(array, sep,   i, str)
46 {
47         str = array[0]
48         for (i = 1; i < length(array); i++)
49                 str = str sep array[i]
50         return str
51 }
52
53 function json_copy(dst, to, src,   key)
54 {
55         if (isarray(src)) {
56                 delete dst[to]
57                 for (key in src) {
58                         dst[to][key]
59                         json_copy(dst[to], key, src[key])
60                 }
61         } else {
62                 dst[to] = src
63         }
64 }
65
66
67 # Write functions
68 function json_write_value(value, pad)
69 {
70         switch (json_gettype(value)) {
71                 case "object": return json_write_object(value, pad)
72                 case "array":  return json_write_array(value, pad)
73                 case "number": return json_write_number(value)
74                 case "string": return json_write_string(value)
75                 case "null":   return "null"
76                 default:       return "error"
77         }
78 }
79
80 function json_write_object(object, pad,   n, i, sort, key, val, data, len, max)
81 {
82         n = asorti(object, sort)
83         for (i = 0; i < n; i++) {
84                 key = json_write_string(sort[i+1])
85                 if (length(key) > max)
86                         max = length(key)
87         }
88         for (i = 0; i < n; i++) {
89                 key = json_write_string(sort[i+1])
90                 val = json_write_value(object[sort[i+1]],
91                         sprintf("%s  %"max"s  ", pad, ""))
92                 data[i] = sprintf("%-"(max+1)"s %s", key":", val)
93         }
94         return "{ " json_join(data, ",\n  " pad) " }"
95 }
96
97 function json_write_array(array, pad,   i, data)
98 {
99         for (i = 0; i < length(array); i++)
100                 data[i] = json_write_value(array[i], pad "  ")
101         return "[ " json_join(data, ",\n  " pad) " ]"
102 }
103
104 function json_write_number(number)
105 {
106         return "" number ""
107 }
108
109 function json_write_string(string)
110 {
111         # todo: special characters
112         return "\"" string "\""
113 }
114
115
116 # Read functions
117 function json_tokenize(str, tokens,   i, line, items, table, type, found)
118 {
119         table["term"]  = "^[\\[\\]{}:,]"
120         table["str"]   = "^\"[^\"]*\""
121         table["num"]   = "^[+-]?[0-9]+(.[0-9]+)?"
122         table["var"]   = "^(true|false|null)"
123         table["space"] = "^[ \\t]+"
124         table["line"]  = "^\n"
125         i = 0;
126         line = 1;
127         while (length(str) > 0) {
128                 found = 0
129                 for (type in table) {
130                         #print "match: str=["str"] type="type" regex=/"table[type]"/"
131                         if (match(str, table[type], items) > 0) {
132                                 #print "       len="RLENGTH" item=["items[0]"]"
133                                 if (type == "line")
134                                         line++;
135                                 if (type == "term")
136                                         type = items[0]
137                                 if (type != "space" && type != "line") {
138                                         tokens[i]["line"] = line
139                                         tokens[i]["type"] = type
140                                         tokens[i]["text"] = items[0]
141                                         i++
142                                 }
143                                 str = substr(str, RLENGTH+1)
144                                 found = 1
145                                 break
146                         }
147                 }
148                 if (!found) {
149                         debug("line " line ": error tokenizing")
150                         return 0
151                 }
152         }
153         return i
154
155         #for (i = 0; i < length(tokens); i++)
156         #       printf "%-3s %-5s [%s]\n", i":",
157         #               tokens[i]["type"], tokens[i]["text"]
158 }
159
160 function json_parse_value(tokens, i, value, key,   line, type, text)
161 {
162         if (!i    ) i = 0;
163         if (!depth) depth = 0
164         depth++
165         line = tokens[i]["line"]
166         type = tokens[i]["type"]
167         text = tokens[i]["text"]
168         #printf "parse %d: i=%-2d type=%-3s text=%s\n", depth, i, type, text
169         switch (type) {
170                 case "{":   i = json_parse_object(tokens, i, value, key); break
171                 case "[":   i = json_parse_array(tokens,  i, value, key); break
172                 case "str": i = json_parse_string(tokens, i, value, key); break
173                 case "num": i = json_parse_number(tokens, i, value, key); break
174                 case "var": i = json_parse_var(tokens,    i, value, key); break
175                 default:    debug("line "line": error type="type" text="text); return 0
176         }
177         depth--
178         return i
179 }
180
181 function json_parse_object(tokens, i, value, key,   object, k, v)
182 {
183         if (tokens[i++]["text"] != "{")
184                 return 0;
185
186         do {
187                 delete k
188                 delete v
189                 if (!(i=json_parse_value(tokens, i, k, 0)))
190                         return 0
191                 if (tokens[i++]["text"] != ":")
192                         return 0
193                 if (!(i=json_parse_value(tokens, i, v, 0)))
194                         return 0
195                 json_copy(object, k[0], v[0]) 
196         } while (tokens[i++]["text"] == ",")
197         i--
198
199         if (tokens[i++]["text"] != "}")
200                 return 0;
201
202         json_copy(value, key, object)
203         return i
204 }
205
206 function json_parse_array(tokens, i, value, key,   array, k, v)
207 {
208         if (tokens[i++]["text"] != "[")
209                 return 0;
210
211         do {
212                 delete v
213                 if (!(i=json_parse_value(tokens, i, v, 0)))
214                         return 0
215                 json_copy(array, k++, v[0]) 
216         } while (tokens[i++]["text"] == ",")
217         i--
218
219         if (tokens[i++]["text"] != "]")
220                 return 0;
221
222         json_copy(value, key, array)
223         return i
224 }
225
226 function json_parse_number(tokens, i, value, key,   text)
227 {
228         text = tokens[i++]["text"]
229         json_copy(value, key, text + 0)
230         #print "parse_number: " (text + 0)
231         return i
232 }
233
234 function json_parse_string(tokens, i, value, key,   text)
235 {
236         text = tokens[i++]["text"]
237         len  = length(text);
238         text = len == 2 ? "" : substr(text, 2, len-2)
239         json_copy(value, key, text)
240         #print "parse_string: [" text "]"
241         return i
242 }
243
244 function json_parse_var(tokens, i, value, key,   text, null)
245 {
246         switch (tokens[i++]["text"]) {
247                 case "true":  json_copy(value, key, 1==1); break;
248                 case "false": json_copy(value, key, 1==2); break;
249                 case "null":  json_copy(value, key, null); break;
250         }
251         #print "parse_var: " text " -> " text == "true"
252         return i
253 }
254
255
256 # Nice API?
257 function json_load(file, var,   line, text, tokens, data, key)
258 {
259         while ((getline line < file) > 0)
260                 text = text line
261         close(file)
262         if (!json_tokenize(text, tokens))
263                 return ""
264         if (!json_parse_value(tokens, 0, data, 0))
265                 return ""
266         if (!isarray(data[0]))
267                 return data[0]
268         for (key in data[0])
269                 json_copy(var, key, data[0][key])
270         return 1
271 }
272
273 function json_save(file, var,   cmd, tmp)
274 {
275         cmd = "mktemp " file ".XXX"
276         cmd | getline tmp
277         close(cmd)
278         print json_write_value(var) > tmp
279         close(tmp)
280         system("mv " tmp " " file)
281 }
282
283
284 # Test functions
285 function json_test_write()
286 {
287         num      = 42
288         str      = "hello, world"
289         arr[0]   = "zero"
290         arr[1]   = "one"
291         arr[2]   = "two"
292         obj["A"] = "a!"
293         obj["B"] = "b!"
294         obj["C"] = "c!"
295         json_copy(mix, "number", num);
296         json_copy(mix, "str",    str);
297         json_copy(mix, "arr",    arr);
298         json_copy(mix, "obj",    obj);
299         json_copy(dub, "first",  mix);
300         json_copy(dub, "second", mix);
301         print json_write_value(num)
302         print json_write_value(str)
303         print json_write_value(arr)
304         print json_write_value(obj)
305         print json_write_value(mix)
306         print json_write_value(dub)
307 }
308
309 function json_test_read()
310 {
311         json_tokenize("[8, \"abc\", 9]", tokens)
312         json_parse_value(tokens, 0, array, 0)
313         print json_write_value(array[0])
314
315         json_tokenize("{\"abc\": 1, \"def\": 2}", tokens)
316         json_parse_value(tokens, 0, array, 0)
317         print json_write_value(array[0])
318
319         json = "{ \"first\":  { \"arr\":    [ \"\",             \n" \
320                "                              -1,               \n" \
321                "                              1.2,              \n" \
322                "                              true,             \n" \
323                "                              false,            \n" \
324                "                              null    ],        \n" \
325                "                \"number\": 42,                 \n" \
326                "                \"obj\":    { \"A\": \"a!\",    \n" \
327                "                              \"B\": \"b!\",    \n" \
328                "                              \"C\": \"c!\" },  \n" \
329                "                \"str\":    \"hello, world\" }, \n" \
330                "  \"second\": { \"arr\":    [ \"zero\",         \n" \
331                "                              \"one\",          \n" \
332                "                              \"two\" ],        \n" \
333                "                \"number\": 42,                 \n" \
334                "                \"obj\":    { \"A\": \"a!\",    \n" \
335                "                              \"B\": \"b!\",    \n" \
336                "                              \"C\": \"c!\" },  \n" \
337                "                \"str\":    \"hello, world\" } }\n"
338
339         json_tokenize(json, tokens)
340         json_parse_value(tokens, 0, array, 0)
341         print json_write_value(array[0])
342 }
343
344 function json_test_files()
345 {
346         print "load: [" json_load("email.txt", mail) "]"
347         print "mail: "  json_write_value(mail, "      ")
348         mail["andy753421"] = "andy753421@gmail.com"
349         mail["andy"]       = "andy@gmail.com"
350         mail["somebody"]   = "foo@example.com"
351         print "mail: "  json_write_value(mail, "      ")
352         print "save: [" json_save("email.txt", mail) "]"
353 }
354
355 # Main
356 BEGIN {
357         #json_test_write()
358         #json_test_read()
359         #json_test_files()
360 }