]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/lib/json.qc
Basic JSON parser
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / json.qc
1 #include "test.qh"
2
3 STRING_ITERATOR(_json, string_null, 0);
4
5 /** parse a json object */
6 bool _json_parse_object();
7     bool _json_parse_members();
8         bool _json_parse_pair();
9 bool _json_parse_array();
10     bool _json_parse_elements();
11 bool _json_parse_value();
12     bool _json_parse_true();
13     bool _json_parse_false();
14     bool _json_parse_null();
15 bool _json_parse_string();
16 bool _json_parse_number();
17     bool _json_parse_int();
18
19 #define JSON_BEGIN() int __i = STRING_ITERATOR_SAVE(_json)
20 #define JSON_FAIL(reason) goto fail
21 #define JSON_END() \
22    return true; \
23 :fail \
24    STRING_ITERATOR_LOAD(_json, __i); \
25    return false;
26
27 bool _json_parse_object() {
28     JSON_BEGIN();
29     if (STRING_ITERATOR_GET(_json) != '{') JSON_FAIL("expected '{'");
30     LOG_INFO(">> object\n");
31     _json_parse_members();
32     if (STRING_ITERATOR_GET(_json) != '}') JSON_FAIL("expected '}'");
33     LOG_INFO("<< object\n");
34     JSON_END();
35 }
36
37     bool _json_parse_members() {
38         JSON_BEGIN();
39         if (!_json_parse_pair()) JSON_FAIL("expected pair");
40         if (STRING_ITERATOR_PEEK(_json) == ',') {
41             STRING_ITERATOR_NEXT(_json);
42             if (!_json_parse_members()) JSON_FAIL("expected pair");
43         }
44         JSON_END();
45     }
46
47         bool _json_parse_pair() {
48             JSON_BEGIN();
49             if (!_json_parse_string()) JSON_FAIL("expected string");
50             if (STRING_ITERATOR_GET(_json) != ':') JSON_FAIL("expected ':'");
51             if (!_json_parse_value()) JSON_FAIL("expected value");
52             JSON_END();
53         }
54
55 bool _json_parse_array() {
56     JSON_BEGIN();
57     if (STRING_ITERATOR_GET(_json) != '[') JSON_FAIL("expected '['");
58     LOG_INFO(">> array\n");
59     _json_parse_elements();
60     if (STRING_ITERATOR_GET(_json) != ']') JSON_FAIL("expected ']'");
61     LOG_INFO("<< array\n");
62     JSON_END();
63 }
64
65     bool _json_parse_elements() {
66         JSON_BEGIN();
67         if (!_json_parse_value()) JSON_FAIL("expected value");
68         if (STRING_ITERATOR_PEEK(_json) == ',') {
69             STRING_ITERATOR_NEXT(_json);
70             if (!_json_parse_elements()) JSON_FAIL("expected value");
71         }
72         JSON_END();
73     }
74
75 bool _json_parse_value() {
76     JSON_BEGIN();
77     if (!(_json_parse_string()
78         || _json_parse_number()
79         || _json_parse_object()
80         || _json_parse_array()
81         || _json_parse_true()
82         || _json_parse_false()
83         || _json_parse_null())) JSON_FAIL("expected value");
84     JSON_END();
85 }
86
87     bool _json_parse_true() {
88         JSON_BEGIN();
89         if (!(STRING_ITERATOR_GET(_json) == 't'
90             && STRING_ITERATOR_GET(_json) == 'r'
91             && STRING_ITERATOR_GET(_json) == 'u'
92             && STRING_ITERATOR_GET(_json) == 'e'))
93             JSON_FAIL("expected 'true'");
94         LOG_INFO(">> bool (true)\n");
95         JSON_END();
96     }
97
98     bool _json_parse_false() {
99         JSON_BEGIN();
100         if (!(STRING_ITERATOR_GET(_json) == 'f'
101             && STRING_ITERATOR_GET(_json) == 'a'
102             && STRING_ITERATOR_GET(_json) == 'l'
103             && STRING_ITERATOR_GET(_json) == 's'
104             && STRING_ITERATOR_GET(_json) == 'e'))
105             JSON_FAIL("expected 'false'");
106         LOG_INFO(">> bool (false)\n");
107         JSON_END();
108     }
109
110     bool _json_parse_null() {
111         JSON_BEGIN();
112         if (!(STRING_ITERATOR_GET(_json) == 'n'
113             && STRING_ITERATOR_GET(_json) == 'u'
114             && STRING_ITERATOR_GET(_json) == 'l'
115             && STRING_ITERATOR_GET(_json) == 'l'))
116             JSON_FAIL("expected 'null'");
117         LOG_INFO(">> null\n");
118         JSON_END();
119     }
120
121 bool _json_parse_string() {
122     JSON_BEGIN();
123     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected opening '\"'");
124     string s = "";
125     for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
126         if (c == '"') {
127             STRING_ITERATOR_UNGET(_json);
128             break;
129         } else if (c == '\\') {
130             string esc;
131             switch (STRING_ITERATOR_GET(_json)) {
132                 default:
133                     JSON_FAIL("expected ( '\"' | '\\' | 'n' | 't' )");
134                 case '"': esc = "\""; break;
135                 case '\\': esc = "\\"; break;
136                 case 'n': esc = "\n"; break;
137                 case 't': esc = "\t"; break;
138             }
139             s = strcat(s, esc);
140         } else {
141             s = strcat(s, chr2str(c));
142         }
143     }
144     if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected closing '\"'");
145     LOG_INFOF(">> string ('%s')\n", s);
146     JSON_END();
147 }
148
149 bool _json_parse_number() {
150     JSON_BEGIN();
151     if (!_json_parse_int()) JSON_FAIL("expected int");
152     JSON_END();
153 }
154
155     bool _json_parse_int() {
156         JSON_BEGIN();
157         string s = "";
158         for (int c; (c = STRING_ITERATOR_GET(_json)); ) {
159             if (!(c >= '0' && c <= '9')) {
160                 STRING_ITERATOR_UNGET(_json);
161                 break;
162             }
163             if (s == "" && c == '0') JSON_FAIL("expected [1-9]");
164             s = strcat(s, chr2str(c));
165         }
166         if (s == "") JSON_FAIL("expected int");
167         int i = stof(s);
168         LOG_INFOF(">> int (%d)\n", i);
169         JSON_END();
170     }
171
172 bool json_parse(string in) {
173     // TODO: remove insignificant whitespace
174     STRING_ITERATOR_SET(_json, in, 0);
175     return _json_parse_object();
176 }
177
178 #undef JSON_BEGIN
179 #undef JSON_FAIL
180 #undef JSON_END
181
182 TEST(json, Parse)
183 {
184     EXPECT_EQ(true, json_parse("{\"string\":\"string\",\"int\":123,\"bool\":true,\"null\":null,\"obj\":{\"arr\":[1,2,3]}}"));
185         SUCCEED();
186 }