Branch data Line data Source code
1 : : /*
2 : : * The MIT License (MIT)
3 : : *
4 : : * Copyright (c) 2024 Yaroslav Riabtsev <yaroslav.riabtsev@rwth-aachen.de>
5 : : *
6 : : * Permission is hereby granted, free of charge, to any person obtaining a copy
7 : : * of this software and associated documentation files (the "Software"), to deal
8 : : * in the Software without restriction, including without limitation the rights
9 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 : : * copies of the Software, and to permit persons to whom the Software is
11 : : * furnished to do so, subject to the following conditions:
12 : : *
13 : : * The above copyright notice and this permission notice shall be included
14 : : * in all copies or substantial portions of the Software.
15 : : *
16 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 : : * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 : : * SOFTWARE.
23 : : */
24 : :
25 : : #include "parser.hpp"
26 : :
27 : : #include <cassert>
28 : : #include <ranges>
29 : : #include <utility>
30 : :
31 : 34 : std::runtime_error parser_lib::parser::throw_message(
32 : : const std::string& message, const std::source_location location
33 : : ) const {
34 [ + - ]: 34 : std::ostringstream oss;
35 [ + - + - : 34 : oss << "[Parser-Error] " << message << ". ";
+ - ]
36 : : #ifndef NDEBUG
37 [ + - ]: 34 : if (!ifs.is_open()) {
38 [ + - ]: 34 : oss << "No input stream open. ";
39 : : }
40 [ + + ]: 34 : if (!valid()) {
41 [ + - ]: 14 : oss << "Position is out of range. Line: " << (line + 1)
42 [ + - + - : 14 : << ", position: " << (pos + 1) << " exceeds available input. ";
+ - + - ]
43 : : } else {
44 : 20 : const char current_char = peek();
45 : : oss << "Character '" << current_char
46 [ + - + - : 20 : << "' (ASCII: " << static_cast<int>(current_char)
+ - ]
47 [ + - + - : 20 : << ") was found at line " << (line + 1) << ", position "
+ - + - ]
48 [ + - + - ]: 20 : << (pos + 1) << ". ";
49 : : }
50 [ + - + - : 34 : oss << "In file: " << location.file_name() << '(' << location.line() << ':'
+ - + - +
- ]
51 [ + - + - : 34 : << location.column() << ") `" << location.function_name() << "`";
+ - + - ]
52 : : #endif
53 [ + - + - ]: 68 : return std::runtime_error(oss.str());
54 : 34 : }
55 : :
56 : 106 : parser_lib::parser::parser(std::string& buffer)
57 : 106 : : buffer(std::move(buffer))
58 : 106 : , pos(0)
59 : 106 : , line(0) { }
60 : :
61 : 0 : parser_lib::parser::parser(const std::stringstream& ss)
62 [ # # ]: 0 : : buffer(ss.str())
63 : 0 : , pos(0)
64 : 0 : , line(0) { }
65 : :
66 : 0 : parser_lib::parser::parser(std::ifstream& is)
67 : 0 : : ifs(move(is)) {
68 [ # # ]: 0 : read_line();
69 : 0 : }
70 : :
71 : 5 : parser_lib::parser::parser(const std::filesystem::path& path) {
72 [ + - ]: 5 : ifs.open(path);
73 [ + + ]: 5 : if (!ifs.is_open()) {
74 : : throw std::invalid_argument(
75 [ + - ]: 2 : "failed to open file with path: " + path.string()
76 [ + - + - ]: 3 : );
77 : : }
78 [ + - ]: 4 : read_line();
79 : 6 : }
80 : :
81 : 110 : void parser_lib::parser::completely_parse_json(
82 : : std::shared_ptr<json_lib::json>& result, const bool dynamic
83 : : ) {
84 : 110 : parse_json(result, dynamic);
85 : 74 : nonessential();
86 [ + + ]: 74 : if (result == nullptr) {
87 [ + - + - ]: 9 : throw throw_message("json is empty");
88 : : }
89 [ + + ]: 71 : if (valid()) {
90 [ + - + - ]: 6 : throw throw_message("invalid json");
91 : : }
92 : 69 : }
93 : :
94 : 2002973 : size_t parser_lib::parser::get_pos() const {
95 [ - + ]: 2002973 : assert(pos >= 0 && "stream is not empty");
96 : 2002973 : return static_cast<size_t>(pos);
97 : : }
98 : :
99 : 703629 : bool parser_lib::parser::valid() const {
100 [ + + + - ]: 703629 : return pos >= 0 && get_pos() < buffer.size();
101 : : }
102 : :
103 : 510238 : void parser_lib::parser::next() {
104 [ - + ]: 510238 : assert(pos >= 0 && "stream is not empty");
105 : 510238 : ++pos;
106 [ + + ]: 510238 : if (get_pos() >= buffer.size()) {
107 : 5077 : read_line();
108 : : }
109 : 510238 : }
110 : :
111 : 5081 : void parser_lib::parser::read_line() {
112 : 5081 : ++line;
113 : 5081 : buffer.clear();
114 : 5081 : pos = -1;
115 [ + + ]: 5081 : if (ifs.eof()) {
116 : 4 : ifs.close();
117 : : }
118 [ + + ]: 5081 : if (!ifs.is_open()) {
119 : 90 : return;
120 : : }
121 : 4991 : std::getline(ifs, buffer);
122 : 4991 : buffer += '\n';
123 : 4991 : pos = 0;
124 : : }
125 : :
126 : 789530 : char parser_lib::parser::peek() const { return buffer[get_pos()]; }
127 : :
128 : 8751 : char parser_lib::parser::get() {
129 : 8751 : const char peek_char = peek();
130 : 8751 : next();
131 : 8751 : return peek_char;
132 : : }
133 : :
134 : 15 : bool parser_lib::parser::check_ahead(const char expected) const {
135 [ - + ]: 15 : if (!valid()) {
136 : 0 : return false;
137 : : }
138 : 15 : const size_t next_index = get_pos() + 1;
139 [ - + ]: 15 : if (next_index >= buffer.size()) {
140 : 0 : return false;
141 : : }
142 : 15 : return buffer[next_index] == expected;
143 : : }
144 : :
145 : 25144 : bool parser_lib::parser::separator(const char val) {
146 : 25144 : nonessential();
147 [ + + + + : 25144 : if (valid() && peek() == val) {
+ + ]
148 : 22705 : next();
149 : 22705 : nonessential();
150 : 22705 : return true;
151 : : }
152 : 2439 : return false;
153 : : }
154 : :
155 : 67329 : void parser_lib::parser::nonessential() {
156 [ + + + + : 115570 : while (valid() && std::isspace(peek())) {
+ + ]
157 : 48241 : next();
158 : : }
159 [ + + + + : 67329 : if (valid() && peek() == '/' && check_ahead('/')) {
+ - + + ]
160 [ + - + + : 387 : while (valid() && get() != '\n')
+ + ]
161 : : ;
162 : 15 : nonessential();
163 : : }
164 : 67329 : }
165 : :
166 : 96 : std::string parser_lib::parser::parse_keyword() {
167 [ + - - + : 96 : assert(
- - ]
168 : : valid() && (std::isalpha(peek()) || peek() == '_')
169 : : && "expected keyword starting with a letter"
170 : : );
171 : 96 : std::string keyword;
172 : : do {
173 [ + - + - ]: 452 : keyword += get();
174 [ + + + + : 452 : } while (valid() && (std::isalnum(peek()) || peek() == '_'));
- + + + ]
175 : 96 : return keyword;
176 : 0 : }
177 : :
178 : 19488 : std::string parser_lib::parser::parse_string() {
179 [ + - + - ]: 19488 : assert(valid() && peek() == '\"' && "expected opening quote");
180 : 19488 : next();
181 : 19488 : std::string value;
182 : 19488 : bool is_backslash = false;
183 : : do {
184 : 406066 : const char current = peek();
185 [ + + ]: 406066 : if (is_backslash) {
186 [ + + + + : 21 : switch (current) {
+ + + + +
+ ]
187 : 5 : case '\"':
188 [ + - ]: 5 : value += '\"';
189 : 5 : break;
190 : 5 : case '\\':
191 [ + - ]: 5 : value += '\\';
192 : 5 : break;
193 : 1 : case '/':
194 [ + - ]: 1 : value += '/';
195 : 1 : break;
196 : 1 : case 'b':
197 [ + - ]: 1 : value += '\b';
198 : 1 : break;
199 : 1 : case 'f':
200 [ + - ]: 1 : value += '\f';
201 : 1 : break;
202 : 2 : case 'n':
203 [ + - ]: 2 : value += '\n';
204 : 2 : break;
205 : 1 : case 'r':
206 [ + - ]: 1 : value += '\r';
207 : 1 : break;
208 : 1 : case 't':
209 [ + - ]: 1 : value += '\t';
210 : 1 : break;
211 : 2 : case 'u': {
212 : 2 : std::string hex;
213 [ + + ]: 8 : for (int i = 0; i < 4; ++i) {
214 [ + - ]: 7 : next();
215 [ + - + + : 7 : if (!valid() || !isxdigit(peek())) {
+ + ]
216 : : throw throw_message(
217 : : "invalid Unicode escape sequence in JSON string"
218 [ + - + - ]: 3 : );
219 : : }
220 [ + - ]: 6 : hex += peek();
221 : : }
222 [ + - ]: 1 : const int codepoint = std::stoi(hex, nullptr, 16);
223 : : std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t>
224 [ + - ]: 1 : converter;
225 [ + - + - ]: 1 : value += converter.to_bytes(static_cast<char32_t>(codepoint));
226 : 1 : break;
227 : 2 : }
228 : 2 : default:
229 [ + - + - ]: 6 : throw throw_message("invalid escape sequence in JSON string");
230 : : }
231 : 18 : is_backslash = false;
232 [ + + ]: 406045 : } else if (current == '\\') {
233 : 21 : is_backslash = true;
234 [ + + ]: 406024 : } else if (current == '\"') {
235 : 19484 : break;
236 : : } else {
237 [ + - ]: 386540 : value += current;
238 : : }
239 [ + - ]: 386579 : next();
240 [ + + ]: 386579 : } while (valid());
241 [ + + - + : 19485 : if (!valid() || peek() != '\"') {
+ + ]
242 [ + - + - ]: 3 : throw throw_message("invalid JSON string: Missing closing quote");
243 : : }
244 [ + - ]: 19484 : next();
245 : 19484 : return value;
246 : 4 : }
247 : :
248 : 3306 : std::tuple<std::string, bool> parser_lib::parser::parse_number() {
249 [ + - ]: 3306 : nonessential();
250 : 3306 : std::string number;
251 : 3306 : bool is_float = false;
252 [ + - + + : 3306 : if (valid() && peek() == '-') {
+ + ]
253 [ + - + - ]: 4 : number += get();
254 : : }
255 [ + + + + : 3306 : if (valid() && peek() == '0') {
+ + ]
256 [ + - + - ]: 16 : number += get();
257 [ + + + + : 16 : if (valid() && std::isdigit(peek())) {
+ + ]
258 : : throw throw_message(
259 : : "invalid number format: leading zeros are not allowed"
260 [ + - + - ]: 3 : );
261 : : }
262 [ + + + - : 3290 : } else if (valid() && std::isdigit(peek())) {
+ + ]
263 : : do {
264 [ + - + - ]: 5708 : number += get();
265 [ + + + + : 5708 : } while (valid() && std::isdigit(peek()));
+ + ]
266 : : } else {
267 [ + - + - ]: 3 : throw throw_message("invalid number format: expected digit");
268 : : }
269 [ + + + + : 3304 : if (valid() && peek() == '.') {
+ + ]
270 : 795 : is_float = true;
271 [ + - + - ]: 795 : number += get();
272 [ + + - + : 795 : if (!valid() || !std::isdigit(peek())) {
+ + ]
273 : : throw throw_message(
274 : : "invalid number format: expected digit after decimal"
275 [ + - + - ]: 3 : );
276 : : }
277 [ + + + + : 2170 : while (valid() && std::isdigit(peek())) {
+ + ]
278 [ + - + - ]: 1376 : number += get();
279 : : }
280 : : }
281 [ + + + + : 3303 : if (valid() && (peek() == 'e' || peek() == 'E')) {
- + + + ]
282 : 5 : is_float = true;
283 [ + - + - ]: 5 : number += get();
284 [ + + + + : 5 : if (valid() && (peek() == '+' || peek() == '-')) {
+ + + + ]
285 [ + - + - ]: 3 : number += get();
286 : : }
287 [ + + - + : 5 : if (!valid() || !std::isdigit(peek())) {
+ + ]
288 : : throw throw_message(
289 : : "invalid number format: expected digit after exponent"
290 [ + - + - ]: 3 : );
291 : : }
292 [ + + + - : 9 : while (valid() && std::isdigit(peek())) {
+ + ]
293 [ + - + - ]: 5 : number += get();
294 : : }
295 : : }
296 [ + - ]: 3302 : assert(!number.empty() && "number is not empty");
297 [ + - ]: 6604 : return { number, is_float };
298 : 3306 : }
299 : :
300 : 1138 : void parser_lib::parser::parse_array_item(
301 : : std::vector<std::shared_ptr<json_lib::json>>& children, const bool dynamic
302 : : ) {
303 : 1138 : std::shared_ptr<json_lib::json> child;
304 [ + + ]: 1138 : parse_json(child, dynamic);
305 [ + - ]: 1135 : children.emplace_back(child);
306 : 1138 : }
307 : :
308 : 20 : void parser_lib::parser::parse_set_item(
309 : : std::vector<std::shared_ptr<reference_lib::json_reference>>& children, bool
310 : : ) {
311 : : auto tail = std::make_shared<reference_lib::json_reference>(
312 : 0 : reference_lib::ref_head_type::accessor
313 [ + - ]: 20 : );
314 [ + - ]: 20 : parse_tail(tail);
315 [ + - + + ]: 20 : if (tail->length() == 0) {
316 [ + - + - ]: 6 : throw throw_message("expected path");
317 : : }
318 [ + - ]: 18 : children.emplace_back(tail);
319 : 20 : }
320 : :
321 : 12001 : void parser_lib::parser::parse_object_item(
322 : : std::vector<std::pair<std::string, std::shared_ptr<json_lib::json>>>&
323 : : children,
324 : : const bool dynamic
325 : : ) {
326 [ + - + + : 12001 : if (!valid() || peek() != '\"') {
+ + ]
327 [ + - + - ]: 9 : throw throw_message("expected key as a string");
328 : : }
329 [ + - ]: 11998 : std::string key = parse_string();
330 [ + - + + ]: 11998 : if (!separator(':')) {
331 [ + - + - ]: 3 : throw throw_message("expected key-value separator for json object");
332 : : }
333 : 11997 : std::shared_ptr<json_lib::json> value;
334 [ + + ]: 11997 : parse_json(value, dynamic);
335 [ + - ]: 11993 : children.emplace_back(key, value);
336 : 12002 : }
337 : :
338 : 374 : bool parser_lib::parser::parse_accessor(
339 : : std::shared_ptr<json_lib::json>& accessor
340 : : ) {
341 : 374 : nonessential();
342 [ + + ]: 374 : if (!valid()) {
343 : 36 : return false;
344 : : }
345 [ + + ]: 338 : if (peek() == '.') {
346 [ + - ]: 60 : next();
347 [ + + + - : 60 : if (!std::isalpha(peek()) && peek() != '_') {
+ + ]
348 [ + - + - ]: 3 : throw throw_message("invalid const accessor");
349 : : }
350 [ + - ]: 59 : const std::string keyword = parse_keyword();
351 [ + + - + : 59 : if (valid() && peek() == '(') {
- + ]
352 [ # # # # ]: 0 : throw throw_message("suffix-functions not yet supported");
353 : : next();
354 : : const auto function
355 : : = std::make_shared<reference_lib::json_function>(keyword);
356 : : function->set_args(
357 : : parse_collection<std::vector<std::shared_ptr<json_lib::json>>>(
358 : : true, ')', &parser::parse_array_item
359 : : )
360 : : );
361 : : accessor = function;
362 : : } else {
363 [ + - ]: 59 : accessor = std::make_shared<json_lib::json_string>(keyword);
364 : : }
365 [ + + ]: 337 : } else if (peek() == '[') {
366 [ + - ]: 55 : next();
367 : : auto keys
368 : : = parse_collection<std::vector<std::shared_ptr<json_lib::json>>>(
369 : : true, ']', &parser::parse_array_item
370 [ + + ]: 55 : );
371 [ + + ]: 54 : if (keys.size() == 1) {
372 : 51 : accessor = keys[0];
373 : : } else {
374 : : std::vector<std::shared_ptr<reference_lib::json_reference>>
375 : 3 : ref_keys;
376 [ + - ]: 3 : ref_keys.reserve(keys.size());
377 [ + + ]: 17 : for (const auto& key : keys) {
378 : : auto ref_accessor
379 : : = std::make_shared<reference_lib::json_reference>(
380 : 0 : reference_lib::ref_head_type::accessor
381 [ + - ]: 14 : );
382 [ + - ]: 14 : ref_accessor->emplace_back(key);
383 [ + - ]: 14 : ref_keys.emplace_back(ref_accessor);
384 : 14 : }
385 [ + - ]: 3 : accessor = std::make_shared<reference_lib::json_set>(ref_keys);
386 : 3 : }
387 [ + + ]: 277 : } else if (peek() == '{') {
388 [ + - ]: 7 : next();
389 : : auto accessors = parse_collection<
390 : : std::vector<std::shared_ptr<reference_lib::json_reference>>>(
391 : : true, '}', &parser::parse_set_item
392 [ + + ]: 7 : );
393 [ + - ]: 5 : accessor = std::make_shared<reference_lib::json_set>(accessors);
394 : 5 : } else {
395 : 216 : return false;
396 : : }
397 : 118 : return true;
398 : : }
399 : :
400 : 262 : void parser_lib::parser::parse_tail(
401 : : const std::shared_ptr<reference_lib::json_reference>& result
402 : : ) {
403 [ + + + + ]: 374 : for (std::shared_ptr<json_lib::json> accessor; parse_accessor(accessor);) {
404 [ + + ]: 118 : result->emplace_back(accessor);
405 : : // todo: reference = reference->ref_value();
406 : 262 : }
407 : 252 : }
408 : :
409 : 242 : void parser_lib::parser::parse_reference(std::shared_ptr<json_lib::json>& result
410 : : ) {
411 : 242 : std::shared_ptr<reference_lib::json_reference> reference;
412 [ + - + + ]: 242 : if (result->type() == json_lib::json_type::reference_json) {
413 : : reference
414 : 60 : = std::dynamic_pointer_cast<reference_lib::json_reference>(result);
415 : : } else {
416 [ + - ]: 182 : reference = std::make_shared<reference_lib::json_reference>(result);
417 : : }
418 [ + + ]: 242 : parse_tail(reference);
419 [ + - ]: 232 : result = reference->value();
420 : 242 : }
421 : :
422 : 13252 : void parser_lib::parser::parse_json(
423 : : std::shared_ptr<json_lib::json>& result, const bool dynamic
424 : : ) {
425 : 13252 : nonessential();
426 [ + + ]: 13252 : if (!valid()) {
427 : 3 : return;
428 : : }
429 [ + + + + : 13249 : if (dynamic && peek() == '@') {
+ + ]
430 : 10 : result = std::make_shared<reference_lib::json_reference>(
431 [ + - ]: 10 : reference_lib::ref_head_type::local
432 : 10 : );
433 : 10 : next();
434 [ + + + + : 13239 : } else if (dynamic && peek() == '$') {
+ + ]
435 : 17 : result = std::make_shared<reference_lib::json_reference>(
436 [ + - ]: 17 : reference_lib::ref_head_type::root
437 : 17 : );
438 : 17 : next();
439 [ + + - + : 13222 : } else if (std::isalpha(peek()) || peek() == '_') {
+ + ]
440 [ + - + - : 37 : if (const std::string keyword = parse_keyword(); keyword == "true") {
+ + ]
441 [ + - ]: 4 : result = std::make_shared<json_lib::json_boolean>(true);
442 [ + - + + ]: 33 : } else if (keyword == "false") {
443 [ + - ]: 2 : result = std::make_shared<json_lib::json_boolean>(false);
444 [ + - + + ]: 31 : } else if (keyword == "null") {
445 [ + - ]: 3 : result = std::make_shared<json_lib::json>();
446 [ + + ]: 28 : } else if (dynamic) {
447 [ + - + + : 27 : if (valid() && peek() == '(') {
+ + ]
448 [ + - ]: 12 : next();
449 : : const auto ref
450 [ + - ]: 12 : = std::make_shared<reference_lib::json_function>(keyword);
451 [ + - ]: 24 : ref->set_args(parse_collection<
452 [ + - ]: 24 : std::vector<std::shared_ptr<json_lib::json>>>(
453 : : dynamic, ')', &parser::parse_array_item
454 : : ));
455 : 12 : result = ref;
456 : 12 : } else {
457 : : const auto ref
458 : : = std::make_shared<reference_lib::json_reference>(
459 : 0 : reference_lib::ref_head_type::root
460 [ + - ]: 15 : );
461 [ + - ]: 30 : ref->emplace_back(
462 [ + - ]: 30 : std::make_shared<json_lib::json_string>(keyword)
463 : : );
464 : 15 : result = ref;
465 : 15 : }
466 : : } else {
467 [ + - + - ]: 3 : throw throw_message("invalid json");
468 : 37 : }
469 [ + + + + : 13185 : } else if (peek() == '-' || std::isdigit(peek())) {
+ + ]
470 [ + + + + ]: 3306 : if (auto [number, is_float] = parse_number(); is_float) {
471 : : // const float value = std::stof(number);
472 [ + - ]: 795 : result = std::make_shared<json_lib::json_real>(number);
473 : : } else {
474 [ + - ]: 2507 : const int value = std::stoi(number);
475 [ + - ]: 2507 : result = std::make_shared<json_lib::json_integer>(value);
476 : 3302 : }
477 [ + + ]: 9879 : } else if (peek() == '\"') {
478 [ + + + - ]: 7490 : result = std::make_shared<json_lib::json_string>(parse_string());
479 [ + + ]: 2389 : } else if (peek() == '[') {
480 [ + - ]: 97 : next();
481 : : auto children
482 : : = parse_collection<std::vector<std::shared_ptr<json_lib::json>>>(
483 : : dynamic, ']', &parser::parse_array_item
484 [ + + ]: 97 : );
485 [ + - ]: 90 : result = std::make_shared<json_lib::json_array>(children);
486 [ + + ]: 90 : if (dynamic) {
487 [ + - ]: 24 : result->touch();
488 : : }
489 [ + + ]: 2382 : } else if (peek() == '{') {
490 [ + - ]: 2281 : next();
491 : : auto children = parse_collection<std::vector<
492 : : std::pair<std::string, std::shared_ptr<json_lib::json>>>>(
493 : : dynamic, '}', &parser::parse_object_item
494 [ + + ]: 2281 : );
495 [ + + ]: 2270 : result = std::make_shared<json_lib::json_object>(children);
496 [ + + ]: 2269 : if (dynamic) {
497 [ + - ]: 12 : result->touch();
498 : : }
499 [ + + + - : 2281 : } else if (dynamic && peek() == '(') {
+ + ]
500 : 7 : next();
501 : 7 : parse_json(result, dynamic);
502 : 7 : nonessential();
503 [ + - + + : 7 : if (!valid() || peek() != ')') {
+ + ]
504 [ + - + - ]: 3 : throw throw_message("invalid json");
505 : : }
506 : 6 : next();
507 : : } else {
508 [ + - + - ]: 12 : throw throw_message("invalid json");
509 : : }
510 [ + + ]: 13216 : if (dynamic) {
511 : 242 : parse_reference(result);
512 : : // todo: math (arithmetic binary operators: +, -, *, /...)
513 : : }
514 : : }
|