]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/lib/json.qc
json: strip insignificant whitespace
[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_int();
21
22 #define JSON_BEGIN() int __i = STRING_ITERATOR_SAVE(_json)
23 #define JSON_FAIL(reason) goto fail
24 #define JSON_END() \
25    return true; \
26 :fail \
27    STRING_ITERATOR_LOAD(_json, __i); \
28    return false;
29 // Current namespace
30 string _json_ns;
31 // Current keys
32 int _json_keys;
33
34 bool _json_parse_object() {
35     JSON_BEGIN();
36     if (STRING_ITERATOR_GET(_json) != '{') JSON_FAIL("expected '{'");
37     WITH(int, _json_keys, bufstr_add(_json_buffer, "", 0), _json_parse_members());
38     if (STRING_ITERATOR_GET(_json) != '}') JSON_FAIL("expected '}'");
39     JSON_END();
40 }
41
42     bool _json_parse_members() {
43         JSON_BEGIN();
44         for (;;) {
45             if (!_json_parse_pair()) JSON_FAIL("expected pair");
46             if (STRING_ITERATOR_PEEK(_json) == ',') {
47                 STRING_ITERATOR_NEXT(_json);
48                 continue;
49             }
50             break;
51         }
52         JSON_END();
53     }
54
55         bool _json_parse_pair() {
56             JSON_BEGIN();
57             if (!_json_parse_string(false)) JSON_FAIL("expected string");
58             string key = _json_temp;
59             bufstr_set(_json_buffer, _json_keys, cons(bufstr_get(_json_buffer, _json_keys), key));
60             key = _json_ns ? strcat(_json_ns, ".", key) : key;
61             bufstr_add(_json_buffer, key, 0);
62             if (STRING_ITERATOR_GET(_json) != ':') JSON_FAIL("expected ':'");
63             bool ret = false; WITH(string, _json_ns, key, ret = _json_parse_value());
64             if (!ret) JSON_FAIL("expected value");
65             JSON_END();
66         }
67
68 bool _json_parse_array() {
69     JSON_BEGIN();
70     if (STRING_ITERATOR_GET(_json) != '[') JSON_FAIL("expected '['");
71     int len = bufstr_add(_json_buffer, "0", 0);
72     bufstr_set(_json_buffer, len - 1, strcat(bufstr_get(_json_buffer, len - 1), ".length"));
73     int n = -1;
74     bool required = false;
75     for (;;) {
76         bufstr_set(_json_buffer, len, ftos(++n));
77         if (!_json_parse_value()) if (required) JSON_FAIL("expected value"); else break;
78         bufstr_add(_json_buffer, strcat(_json_ns, ".", ftos(n)), 0);
79         if (STRING_ITERATOR_PEEK(_json) == ',') {
80             STRING_ITERATOR_NEXT(_json);
81             required = true;
82             continue;
83         }
84         break;
85     }
86     if (STRING_ITERATOR_GET(_json) != ']') JSON_FAIL("expected ']'");
87     JSON_END();
88 }
89
90 bool _json_parse_value() {
91     JSON_BEGIN();
92     if (!(_json_parse_string(true)
93         || _json_parse_number()
94         || _json_parse_object()
95         || _json_parse_array()
96         || _json_parse_true()
97         || _json_parse_false()
98         || _json_parse_null())) JSON_FAIL("expected value");
99     JSON_END();
100 }
101
102     bool _json_parse_true() {
103         JSON_BEGIN();
104         if (!(STRING_ITERATOR_GET(_json) == 't'
105             && STRING_ITERATOR_GET(_json) == 'r'
106             && STRING_ITERATOR_GET(_json) == 'u'
107             && STRING_ITERATOR_GET(_json) == 'e'))
108             JSON_FAIL("expected 'true'");
109         bufstr_add(_json_buffer, "1", 0);
110         JSON_END();
111     }
112
113     bool _json_parse_false() {
114         JSON_BEGIN();
115         if (!(STRING_ITERATOR_GET(_json) == 'f'
116             && STRING_ITERATOR_GET(_json) == 'a'
117             && STRING_ITERATOR_GET(_json) == 'l'
118             && STRING_ITERATOR_GET(_json) == 's'
119             && STRING_ITERATOR_GET(_json) == 'e'))
120             JSON_FAIL("expected 'false'");
121         bufstr_add(_json_buffer, "0", 0);
122         JSON_END();
123     }
124
125     bool _json_parse_null() {
126         JSON_BEGIN();
127         if (!(STRING_ITERATOR_GET(_json) == 'n'
128             && STRING_ITERATOR_GET(_json) == 'u'
129             && STRING_ITERATOR_GET(_json) == 'l'
130             && STRING_ITERATOR_GET(_json) == 'l'))
131             JSON_FAIL("expected 'null'");
132         bufstr_add(_json_buffer, "", 0);
133         JSON_END();
134     }
135
136 bool _json_parse_string(bool add) {
137     JSON_BEGIN();
138     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected opening '\"'");
139     string s = "";
140     for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
141         if (c == '"') {
142             STRING_ITERATOR_UNGET(_json);
143             break;
144         } else if (c == '\\') {
145             string esc;
146             switch (STRING_ITERATOR_GET(_json)) {
147                 default:
148                     JSON_FAIL("expected ( '\"' | '\\' | 'n' | 't' )");
149                 case '"': esc = "\""; break;
150                 case '\\': esc = "\\"; break;
151                 case 'n': esc = "\n"; break;
152                 case 't': esc = "\t"; break;
153             }
154             s = strcat(s, esc);
155         } else {
156             s = strcat(s, chr2str(c));
157         }
158     }
159     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected closing '\"'");
160     if (add) bufstr_add(_json_buffer, s, 0);
161     _json_temp = s;
162     JSON_END();
163 }
164
165 bool _json_parse_number() {
166     JSON_BEGIN();
167     if (!_json_parse_int()) JSON_FAIL("expected number");
168     JSON_END();
169 }
170
171     bool _json_parse_int() {
172         JSON_BEGIN();
173         string s = "";
174         for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
175             if (!(c >= '0' && c <= '9')) {
176                 STRING_ITERATOR_UNGET(_json);
177                 break;
178             }
179             if (s == "" && c == '0') JSON_FAIL("expected [1-9]");
180             s = strcat(s, chr2str(c));
181         }
182         if (s == "") JSON_FAIL("expected int");
183         if (ftos(stof(s)) != s) JSON_FAIL("expected int");
184         bufstr_add(_json_buffer, s, 0);
185         JSON_END();
186     }
187
188 int json_parse(string in) {
189     string trimmed = "";
190     LABEL(trim) {
191         int o = strstrofs(in, "\"", 0);
192         if (o >= 0) {
193             string part = substring(in, 0, o + 1); in = substring(in, o + 1, -1);
194             part = strreplace(" ", "", part);
195             part = strreplace("\n", "", part);
196             trimmed = strcat(trimmed, part);
197             goto trim_str;
198         } else {
199             string part = in;
200             part = strreplace(" ", "", part);
201             part = strreplace("\n", "", part);
202             trimmed = strcat(trimmed, part);
203             goto done;
204         }
205     }
206     LABEL(trim_str) {
207         int o = strstrofs(in, "\"", 0);
208         int esc = strstrofs(in, "\\\"", 0);
209         if (o < esc || esc < 0) {
210             // simple string
211             string part = substring(in, 0, o + 1); in = substring(in, o + 1, -1);
212             trimmed = strcat(trimmed, part);
213             goto trim;
214         } else {
215             // has escape
216             string part = substring(in, 0, esc + 2); in = substring(in, esc + 2, -1);
217             trimmed = strcat(trimmed, part);
218             goto trim_str;
219         }
220     }
221     LABEL(done);
222
223     STRING_ITERATOR_SET(_json, trimmed, 0);
224     _json_buffer = buf_create();
225     bool ret = _json_parse_object();
226     if (!ret) {
227         buf_del(_json_buffer);
228         _json_buffer = -1;
229     }
230     return _json_buffer;
231 }
232
233 #undef JSON_BEGIN
234 #undef JSON_FAIL
235 #undef JSON_END
236
237 TEST(json, Parse)
238 {
239     string s = "{\n\
240     \"m_string\": \"\\\"string\\\"\",\n\
241     \"m_int\": 123,\n\
242     \"m_bool\": true,\n\
243     \"m_null\": null,\n\
244     \"m_obj\": { },\n\
245     \"m_arr\": [ ]\n}"; // "
246     print(s, "\n");
247     int buf = json_parse(s);
248     EXPECT_NE(-1, buf);
249     for (int i = 0, n = buf_getsize(buf); i < n; ++i) {
250         print(bufstr_get(buf, i), "\n");
251     }
252         SUCCEED();
253 }