]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/lib/json.qc
Add an intrusive list for warpzones
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / json.qc
1 #include "test.qh"
2
3 STRING_ITERATOR(_json, string_null, 0);
4 // Store interleaved keys/values in a string buffer
5 int _json_buffer;
6 // Last read string
7 string _json_temp;
8
9 /** parse a json object */
10 bool _json_parse_object();
11     bool _json_parse_members();
12         bool _json_parse_pair();
13 bool _json_parse_array();
14 bool _json_parse_value();
15     bool _json_parse_true();
16     bool _json_parse_false();
17     bool _json_parse_null();
18 bool _json_parse_string(bool add);
19 bool _json_parse_number();
20     bool _json_parse_float();
21     bool _json_parse_int();
22
23 #define JSON_BEGIN() int __i = STRING_ITERATOR_SAVE(_json)
24 #define JSON_FAIL(reason) goto fail
25 #define JSON_END() \
26    return true; \
27 :fail \
28    STRING_ITERATOR_LOAD(_json, __i); \
29    return false;
30 // Current namespace
31 string _json_ns;
32 // Current keys
33 int _json_keys;
34
35 bool _json_parse_object() {
36     JSON_BEGIN();
37     if (STRING_ITERATOR_GET(_json) != '{') JSON_FAIL("expected '{'");
38     WITH(int, _json_keys, bufstr_add(_json_buffer, "", 0), _json_parse_members());
39     if (STRING_ITERATOR_GET(_json) != '}') JSON_FAIL("expected '}'");
40     JSON_END();
41 }
42
43     bool _json_parse_members() {
44         JSON_BEGIN();
45         for (;;) {
46             if (!_json_parse_pair()) JSON_FAIL("expected pair");
47             if (STRING_ITERATOR_PEEK(_json) == ',') {
48                 STRING_ITERATOR_NEXT(_json);
49                 continue;
50             }
51             break;
52         }
53         JSON_END();
54     }
55
56         bool _json_parse_pair() {
57             JSON_BEGIN();
58             if (!_json_parse_string(false)) JSON_FAIL("expected string");
59             string key = _json_temp;
60             bufstr_set(_json_buffer, _json_keys, cons(bufstr_get(_json_buffer, _json_keys), key));
61             key = _json_ns ? strcat(_json_ns, ".", key) : key;
62             bufstr_add(_json_buffer, key, 0);
63             if (STRING_ITERATOR_GET(_json) != ':') JSON_FAIL("expected ':'");
64             bool ret = false; WITH(string, _json_ns, key, ret = _json_parse_value());
65             if (!ret) JSON_FAIL("expected value");
66             JSON_END();
67         }
68
69 bool _json_parse_array() {
70     JSON_BEGIN();
71     if (STRING_ITERATOR_GET(_json) != '[') JSON_FAIL("expected '['");
72     int len = bufstr_add(_json_buffer, "0", 0);
73     if (len) bufstr_set(_json_buffer, len - 1, strcat(bufstr_get(_json_buffer, len - 1), ".length"));
74     bool required = false;
75     for (int n = 0; ; n++) {
76         string key = ftos(n);
77         key = _json_ns ? strcat(_json_ns, ".", key) : key;
78         int it = bufstr_add(_json_buffer, key, 0);
79         bool ret = false; WITH(string, _json_ns, key, ret = _json_parse_value());
80         if (!ret) {
81             bufstr_free(_json_buffer, it);
82             if (required) JSON_FAIL("expected value"); else break;
83         }
84         bufstr_set(_json_buffer, len, ftos(n + 1));
85         if (STRING_ITERATOR_PEEK(_json) == ',') {
86             STRING_ITERATOR_NEXT(_json);
87             required = true;
88             continue;
89         }
90         break;
91     }
92     if (STRING_ITERATOR_GET(_json) != ']') JSON_FAIL("expected ']'");
93     JSON_END();
94 }
95
96 bool _json_parse_value() {
97     JSON_BEGIN();
98     if (!(_json_parse_string(true)
99         || _json_parse_number()
100         || _json_parse_object()
101         || _json_parse_array()
102         || _json_parse_true()
103         || _json_parse_false()
104         || _json_parse_null())) JSON_FAIL("expected value");
105     JSON_END();
106 }
107
108     bool _json_parse_true() {
109         JSON_BEGIN();
110         if (!(STRING_ITERATOR_GET(_json) == 't'
111             && STRING_ITERATOR_GET(_json) == 'r'
112             && STRING_ITERATOR_GET(_json) == 'u'
113             && STRING_ITERATOR_GET(_json) == 'e'))
114             JSON_FAIL("expected 'true'");
115         bufstr_add(_json_buffer, "1", 0);
116         JSON_END();
117     }
118
119     bool _json_parse_false() {
120         JSON_BEGIN();
121         if (!(STRING_ITERATOR_GET(_json) == 'f'
122             && STRING_ITERATOR_GET(_json) == 'a'
123             && STRING_ITERATOR_GET(_json) == 'l'
124             && STRING_ITERATOR_GET(_json) == 's'
125             && STRING_ITERATOR_GET(_json) == 'e'))
126             JSON_FAIL("expected 'false'");
127         bufstr_add(_json_buffer, "0", 0);
128         JSON_END();
129     }
130
131     bool _json_parse_null() {
132         JSON_BEGIN();
133         if (!(STRING_ITERATOR_GET(_json) == 'n'
134             && STRING_ITERATOR_GET(_json) == 'u'
135             && STRING_ITERATOR_GET(_json) == 'l'
136             && STRING_ITERATOR_GET(_json) == 'l'))
137             JSON_FAIL("expected 'null'");
138         bufstr_add(_json_buffer, "", 0);
139         JSON_END();
140     }
141
142 bool _json_parse_string(bool add) {
143     JSON_BEGIN();
144     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected opening '\"'");
145     string s = "";
146     for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
147         if (c == '"') {
148             STRING_ITERATOR_UNGET(_json);
149             break;
150         } else if (c == '\\') {
151             string esc;
152             switch (STRING_ITERATOR_GET(_json)) {
153                 default:
154                     JSON_FAIL("expected ( '\"' | '\\' | 'n' | 't' )");
155                 case '"': esc = "\""; break;
156                 case '\\': esc = "\\"; break;
157                 case 'n': esc = "\n"; break;
158                 case 't': esc = "\t"; break;
159                 case 'u': esc = "\\u"; break; // TODO
160                 case '/': esc = "/"; break;
161             }
162             s = strcat(s, esc);
163         } else {
164             s = strcat(s, chr2str(c));
165         }
166     }
167     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected closing '\"'");
168     if (add) bufstr_add(_json_buffer, s, 0);
169     _json_temp = s;
170     JSON_END();
171 }
172
173 bool _json_parse_number() {
174     JSON_BEGIN();
175     if (!(_json_parse_float() || _json_parse_int())) JSON_FAIL("expected number");
176     JSON_END();
177 }
178
179     bool _json_parse_float() {
180         JSON_BEGIN();
181         string s = "";
182         bool needdot = true;
183         for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
184             if (!(c >= '0' && c <= '9')) {
185                 if (c == '.' && needdot) {
186                     // fine
187                     needdot = false;
188                 } else {
189                     STRING_ITERATOR_UNGET(_json);
190                     break;
191                 }
192             }
193             s = strcat(s, chr2str(c));
194         }
195         if (s == "") JSON_FAIL("expected float");
196         bufstr_add(_json_buffer, s, 0);
197         JSON_END();
198     }
199
200     bool _json_parse_int() {
201         JSON_BEGIN();
202         string s = "";
203         for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
204             if (!(c >= '0' && c <= '9')) {
205                 STRING_ITERATOR_UNGET(_json);
206                 break;
207             }
208             if (s == "" && c == '0') JSON_FAIL("expected [1-9]");
209             s = strcat(s, chr2str(c));
210         }
211         if (s == "") JSON_FAIL("expected int");
212         if (ftos(stof(s)) != s) JSON_FAIL("expected int");
213         bufstr_add(_json_buffer, s, 0);
214         JSON_END();
215     }
216
217 int json_parse(string in, bool() func) {
218     string trimmed = "";
219     LABEL(trim) {
220         int o = strstrofs(in, "\"", 0);
221         if (o >= 0) {
222             string part = substring(in, 0, o + 1); in = substring(in, o + 1, -1);
223             part = strreplace(" ", "", part);
224             part = strreplace("\n", "", part);
225             trimmed = strcat(trimmed, part);
226             goto trim_str;
227         } else {
228             string part = in;
229             part = strreplace(" ", "", part);
230             part = strreplace("\n", "", part);
231             trimmed = strcat(trimmed, part);
232             goto done;
233         }
234     }
235     LABEL(trim_str) {
236         int o = strstrofs(in, "\"", 0);
237         int esc = strstrofs(in, "\\\"", 0);
238         if (o < esc || esc < 0) {
239             // simple string
240             string part = substring(in, 0, o + 1); in = substring(in, o + 1, -1);
241             trimmed = strcat(trimmed, part);
242             goto trim;
243         } else {
244             // has escape
245             string part = substring(in, 0, esc + 2); in = substring(in, esc + 2, -1);
246             trimmed = strcat(trimmed, part);
247             goto trim_str;
248         }
249     }
250     LABEL(done);
251
252     STRING_ITERATOR_SET(_json, trimmed, 0);
253     _json_buffer = buf_create();
254     bool ret = func();
255     if (!ret) {
256         buf_del(_json_buffer);
257         _json_buffer = -1;
258     }
259     return _json_buffer;
260 }
261
262 string json_get(int buf, string key)
263 {
264     for (int i = 1, n = buf_getsize(buf); i < n; i += 2) {
265         if (bufstr_get(buf, i) == key) return bufstr_get(buf, i + 1);
266     }
267     return string_null;
268 }
269
270 void json_del(int buf)
271 {
272     buf_del(buf);
273 }
274
275 void json_dump(int buf)
276 {
277     for (int i = 0, n = buf_getsize(buf); i < n; ++i) {
278         print(bufstr_get(buf, i), "\n");
279     }
280 }
281
282 #undef JSON_BEGIN
283 #undef JSON_FAIL
284 #undef JSON_END
285
286 TEST(json, Parse)
287 {
288     string s = "{\n\
289     \"m_string\": \"\\\"string\\\"\",\n\
290     \"m_int\": 123,\n\
291     \"m_bool\": true,\n\
292     \"m_null\": null,\n\
293     \"m_obj\": { },\n\
294     \"m_arr\": [ ]\n}"; // "
295     print(s, "\n");
296     int buf = json_parse(s, _json_parse_object);
297     EXPECT_NE(-1, buf);
298     json_dump(buf);
299     SUCCEED();
300 }