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