Fix -Wmissing-prototypes/-Wmissing-declarations warnings
[babeltrace.git] / src / param-parse / param-parse.c
1 /*
2 * Copyright 2016-2019 Philippe Proulx <pproulx@efficios.com>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 #include "param-parse.h"
24
25 #include <errno.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "common/assert.h"
29 #include <stdio.h>
30 #include <stdbool.h>
31 #include <inttypes.h>
32 #include <babeltrace2/babeltrace.h>
33 #include "common/common.h"
34 #include <glib.h>
35 #include <sys/types.h>
36
37 /* INI-style parsing FSM states */
38 enum ini_parsing_fsm_state {
39 /* Expect a map key (identifier) */
40 INI_EXPECT_MAP_KEY,
41
42 /* Expect an equal character (`=`) */
43 INI_EXPECT_EQUAL,
44
45 /* Expect a value */
46 INI_EXPECT_VALUE,
47
48 /* Expect a comma character (`,`) */
49 INI_EXPECT_COMMA,
50 };
51
52 /* INI-style parsing state variables */
53 struct ini_parsing_state {
54 /* Lexical scanner (owned by this) */
55 GScanner *scanner;
56
57 /* Output map value object being filled (owned by this) */
58 bt_value *params;
59
60 /* Next expected FSM state */
61 enum ini_parsing_fsm_state expecting;
62
63 /* Last decoded map key (owned by this) */
64 GString *last_map_key;
65
66 /* Complete INI-style string to parse */
67 const char *arg;
68
69 /* Error buffer (weak) */
70 GString *ini_error;
71 };
72
73 /*
74 * Appends an "expecting token" error to the INI-style parsing state's
75 * error buffer.
76 */
77 static
78 void ini_append_error_expecting(struct ini_parsing_state *state,
79 GScanner *scanner, const char *expecting)
80 {
81 size_t i;
82 size_t pos;
83
84 g_string_append_printf(state->ini_error, "Expecting %s:\n", expecting);
85
86 /* Only append error if there's one line */
87 if (strchr(state->arg, '\n') || strlen(state->arg) == 0) {
88 return;
89 }
90
91 g_string_append_printf(state->ini_error, "\n %s\n", state->arg);
92 pos = g_scanner_cur_position(scanner) + 4;
93
94 if (!g_scanner_eof(scanner)) {
95 pos--;
96 }
97
98 for (i = 0; i < pos; ++i) {
99 g_string_append_printf(state->ini_error, " ");
100 }
101
102 g_string_append_printf(state->ini_error, "^\n\n");
103 }
104
105 static
106 void ini_append_oom_error(GString *error)
107 {
108 BT_ASSERT(error);
109 g_string_append(error, "Out of memory\n");
110 }
111
112 /*
113 * Parses the next token as an unsigned integer.
114 */
115 static
116 bt_value *ini_parse_uint(struct ini_parsing_state *state)
117 {
118 bt_value *value = NULL;
119 GTokenType token_type = g_scanner_get_next_token(state->scanner);
120
121 if (token_type != G_TOKEN_INT) {
122 ini_append_error_expecting(state, state->scanner,
123 "integer value");
124 goto end;
125 }
126
127 value = bt_value_integer_unsigned_create_init(
128 state->scanner->value.v_int64);
129
130 end:
131 return value;
132 }
133
134 /*
135 * Parses the next token as a number and returns its negation.
136 */
137 static
138 bt_value *ini_parse_neg_number(struct ini_parsing_state *state)
139 {
140 bt_value *value = NULL;
141 GTokenType token_type = g_scanner_get_next_token(state->scanner);
142
143 switch (token_type) {
144 case G_TOKEN_INT:
145 {
146 /* Negative integer */
147 uint64_t int_val = state->scanner->value.v_int64;
148
149 if (int_val > (((uint64_t) INT64_MAX) + 1)) {
150 g_string_append_printf(state->ini_error,
151 "Integer value -%" PRIu64 " is outside the range of a 64-bit signed integer\n",
152 int_val);
153 } else {
154 value = bt_value_integer_signed_create_init(
155 -((int64_t) int_val));
156 }
157
158 break;
159 }
160 case G_TOKEN_FLOAT:
161 /* Negative floating point number */
162 value = bt_value_real_create_init(
163 -state->scanner->value.v_float);
164 break;
165 default:
166 ini_append_error_expecting(state, state->scanner, "value");
167 break;
168 }
169
170 return value;
171 }
172
173 static bt_value *ini_parse_value(struct ini_parsing_state *state);
174
175 /*
176 * Parses the current and following tokens as an array. Arrays are
177 * formatted as an opening `[`, a list of comma-separated values, and a
178 * closing `]`. For convenience, this function supports an optional
179 * trailing comma after the last value.
180 *
181 * The current token of the parser must be the opening square bracket
182 * (`[`) of the array.
183 */
184 static
185 bt_value *ini_parse_array(struct ini_parsing_state *state)
186 {
187 bt_value *array_value;
188 GTokenType token_type;
189
190 /* The `[` character must have already been ingested */
191 BT_ASSERT(g_scanner_cur_token(state->scanner) == G_TOKEN_CHAR);
192 BT_ASSERT(g_scanner_cur_value(state->scanner).v_char == '[');
193
194 array_value = bt_value_array_create ();
195 if (!array_value) {
196 ini_append_oom_error(state->ini_error);
197 goto error;
198 }
199
200 token_type = g_scanner_get_next_token(state->scanner);
201
202 /* While the current token is not a `]` */
203 while (!(token_type == G_TOKEN_CHAR &&
204 g_scanner_cur_value(state->scanner).v_char == ']')) {
205 bt_value *item_value;
206 bt_value_array_append_element_status append_status;
207
208 /* Parse the item... */
209 item_value = ini_parse_value(state);
210 if (!item_value) {
211 goto error;
212 }
213
214 /* ... and add it to the result array */
215 append_status = bt_value_array_append_element(array_value,
216 item_value);
217 BT_VALUE_PUT_REF_AND_RESET(item_value);
218 if (append_status < 0) {
219 goto error;
220 }
221
222 /*
223 * Ingest the token following the value. It should be
224 * either a comma or closing square bracket.
225 */
226 token_type = g_scanner_get_next_token(state->scanner);
227 if (token_type == G_TOKEN_CHAR &&
228 g_scanner_cur_value(state->scanner).v_char == ',') {
229 /*
230 * Ingest the token following the comma. If it
231 * happens to be a closing square bracket, exit
232 * the loop and we are done (we allow trailing
233 * commas). Otherwise, we are ready for the next
234 * ini_parse_value() call.
235 */
236 token_type = g_scanner_get_next_token(state->scanner);
237 } else if (token_type != G_TOKEN_CHAR ||
238 g_scanner_cur_value(state->scanner).v_char != ']') {
239 ini_append_error_expecting(state, state->scanner,
240 "`,` or `]`");
241 goto error;
242 }
243 }
244
245 goto end;
246
247 error:
248 BT_VALUE_PUT_REF_AND_RESET(array_value);
249
250 end:
251 return array_value;
252 }
253
254 /*
255 * Parses the current and following tokens as a map. Maps are
256 * formatted as an opening `{`, a list of comma-separated entries, and a
257 * closing `}`. And entry is a key (an unquoted string), an equal sign and
258 * a value. For convenience, this function supports an optional trailing comma
259 * after the last value.
260 *
261 * The current token of the parser must be the opening curly bracket
262 * (`{`) of the array.
263 */
264 static
265 bt_value *ini_parse_map(struct ini_parsing_state *state)
266 {
267 bt_value *map_value;
268 GTokenType token_type;
269 gchar *key = NULL;
270
271 /* The `{` character must have already been ingested */
272 BT_ASSERT(g_scanner_cur_token(state->scanner) == G_TOKEN_CHAR);
273 BT_ASSERT(g_scanner_cur_value(state->scanner).v_char == '{');
274
275 map_value = bt_value_map_create ();
276 if (!map_value) {
277 ini_append_oom_error(state->ini_error);
278 goto error;
279 }
280
281 token_type = g_scanner_get_next_token(state->scanner);
282
283 /* While the current token is not a `}` */
284 while (!(token_type == G_TOKEN_CHAR &&
285 g_scanner_cur_value(state->scanner).v_char == '}')) {
286 bt_value *entry_value;
287 bt_value_map_insert_entry_status insert_entry_status;
288
289 /* Expect map key. */
290 if (token_type != G_TOKEN_IDENTIFIER) {
291 ini_append_error_expecting(state, state->scanner,
292 "unquoted map key");
293 goto error;
294 }
295
296 g_free(key);
297 key = g_strdup(g_scanner_cur_value(state->scanner).v_identifier);
298
299 token_type = g_scanner_get_next_token(state->scanner);
300
301 /* Expect equal sign. */
302 if (token_type != G_TOKEN_CHAR ||
303 g_scanner_cur_value(state->scanner).v_char != '=') {
304 ini_append_error_expecting(state,
305 state->scanner, "'='");
306 goto error;
307 }
308
309 token_type = g_scanner_get_next_token(state->scanner);
310
311 /* Parse the entry value... */
312 entry_value = ini_parse_value(state);
313 if (!entry_value) {
314 goto error;
315 }
316
317 /* ... and add it to the result map */
318 insert_entry_status =
319 bt_value_map_insert_entry(map_value, key, entry_value);
320 BT_VALUE_PUT_REF_AND_RESET(entry_value);
321 if (insert_entry_status != BT_VALUE_MAP_INSERT_ENTRY_STATUS_OK) {
322 goto error;
323 }
324
325 /*
326 * Ingest the token following the value. It should be
327 * either a comma or closing curly bracket.
328 */
329 token_type = g_scanner_get_next_token(state->scanner);
330 if (token_type == G_TOKEN_CHAR &&
331 g_scanner_cur_value(state->scanner).v_char == ',') {
332 /*
333 * Ingest the token following the comma. If it
334 * happens to be a closing curly bracket, exit
335 * the loop and we are done (we allow trailing
336 * commas). Otherwise, we are ready for the next
337 * ini_parse_value() call.
338 */
339 token_type = g_scanner_get_next_token(state->scanner);
340 } else if (token_type != G_TOKEN_CHAR ||
341 g_scanner_cur_value(state->scanner).v_char != '}') {
342 ini_append_error_expecting(state, state->scanner,
343 "`,` or `}`");
344 goto error;
345 }
346 }
347
348 goto end;
349 error:
350 BT_VALUE_PUT_REF_AND_RESET(map_value);
351
352 end:
353 g_free(key);
354
355 return map_value;
356 }
357
358 /*
359 * Parses the current token (and the following ones if needed) as a
360 * value, returning it as a `bt_value *`.
361 */
362 static
363 bt_value *ini_parse_value(struct ini_parsing_state *state)
364 {
365 bt_value *value = NULL;
366 GTokenType token_type = state->scanner->token;
367
368 switch (token_type) {
369 case G_TOKEN_CHAR:
370 if (state->scanner->value.v_char == '-') {
371 /* Negative number */
372 value = ini_parse_neg_number(state);
373 } else if (state->scanner->value.v_char == '+') {
374 /* Unsigned integer */
375 value = ini_parse_uint(state);
376 } else if (state->scanner->value.v_char == '[') {
377 /* Array */
378 value = ini_parse_array(state);
379 } else if (state->scanner->value.v_char == '{') {
380 /* Map */
381 value = ini_parse_map(state);
382 } else {
383 ini_append_error_expecting(state, state->scanner, "value");
384 goto end;
385 }
386
387 break;
388 case G_TOKEN_INT:
389 {
390 /* Positive, signed integer */
391 uint64_t int_val = state->scanner->value.v_int64;
392
393 if (int_val > INT64_MAX) {
394 g_string_append_printf(state->ini_error,
395 "Integer value %" PRIu64 " is outside the range of a 64-bit signed integer\n",
396 int_val);
397 goto end;
398 } else {
399 value = bt_value_integer_signed_create_init(
400 (int64_t) int_val);
401 }
402
403 break;
404 }
405 case G_TOKEN_FLOAT:
406 /* Positive floating point number */
407 value = bt_value_real_create_init(state->scanner->value.v_float);
408 break;
409 case G_TOKEN_STRING:
410 /* Quoted string */
411 value = bt_value_string_create_init(state->scanner->value.v_string);
412 break;
413 case G_TOKEN_IDENTIFIER:
414 {
415 /*
416 * Using symbols would be appropriate here, but said
417 * symbols are allowed as map key, so it's easier to
418 * consider everything an identifier.
419 *
420 * If one of the known symbols is not recognized here,
421 * then fall back to creating a string value.
422 */
423 const char *id = state->scanner->value.v_identifier;
424
425 if (strcmp(id, "null") == 0 || strcmp(id, "NULL") == 0 ||
426 strcmp(id, "nul") == 0) {
427 value = bt_value_null;
428 bt_value_get_ref(value);
429 } else if (strcmp(id, "true") == 0 || strcmp(id, "TRUE") == 0 ||
430 strcmp(id, "yes") == 0 ||
431 strcmp(id, "YES") == 0) {
432 value = bt_value_bool_create_init(true);
433 } else if (strcmp(id, "false") == 0 ||
434 strcmp(id, "FALSE") == 0 ||
435 strcmp(id, "no") == 0 ||
436 strcmp(id, "NO") == 0) {
437 value = bt_value_bool_create_init(false);
438 } else {
439 value = bt_value_string_create_init(id);
440 }
441 break;
442 }
443 default:
444 /* Unset return value variable will trigger the error */
445 ini_append_error_expecting(state, state->scanner, "value");
446 break;
447 }
448
449 end:
450 return value;
451 }
452
453 /*
454 * Handles the current state of the INI parser.
455 *
456 * Returns 0 to continue, 1 to end, or a negative value on error.
457 */
458 static
459 int ini_handle_state(struct ini_parsing_state *state)
460 {
461 int ret = 0;
462 GTokenType token_type;
463 bt_value *value = NULL;
464
465 token_type = g_scanner_get_next_token(state->scanner);
466 if (token_type == G_TOKEN_EOF) {
467 if (state->expecting != INI_EXPECT_COMMA) {
468 switch (state->expecting) {
469 case INI_EXPECT_EQUAL:
470 ini_append_error_expecting(state,
471 state->scanner, "`=`");
472 break;
473 case INI_EXPECT_VALUE:
474 ini_append_error_expecting(state,
475 state->scanner, "value");
476 break;
477 case INI_EXPECT_MAP_KEY:
478 ini_append_error_expecting(state,
479 state->scanner, "unquoted map key");
480 break;
481 default:
482 break;
483 }
484 goto error;
485 }
486
487 /* We're done! */
488 ret = 1;
489 goto success;
490 }
491
492 switch (state->expecting) {
493 case INI_EXPECT_MAP_KEY:
494 if (token_type != G_TOKEN_IDENTIFIER) {
495 ini_append_error_expecting(state, state->scanner,
496 "unquoted map key");
497 goto error;
498 }
499
500 g_string_assign(state->last_map_key,
501 state->scanner->value.v_identifier);
502
503 if (bt_value_map_has_entry(state->params,
504 state->last_map_key->str)) {
505 g_string_append_printf(state->ini_error,
506 "Duplicate parameter key: `%s`\n",
507 state->last_map_key->str);
508 goto error;
509 }
510
511 state->expecting = INI_EXPECT_EQUAL;
512 goto success;
513 case INI_EXPECT_EQUAL:
514 if (token_type != G_TOKEN_CHAR) {
515 ini_append_error_expecting(state,
516 state->scanner, "'='");
517 goto error;
518 }
519
520 if (state->scanner->value.v_char != '=') {
521 ini_append_error_expecting(state,
522 state->scanner, "'='");
523 goto error;
524 }
525
526 state->expecting = INI_EXPECT_VALUE;
527 goto success;
528 case INI_EXPECT_VALUE:
529 {
530 value = ini_parse_value(state);
531 if (!value) {
532 goto error;
533 }
534
535 state->expecting = INI_EXPECT_COMMA;
536 goto success;
537 }
538 case INI_EXPECT_COMMA:
539 if (token_type != G_TOKEN_CHAR) {
540 ini_append_error_expecting(state,
541 state->scanner, "','");
542 goto error;
543 }
544
545 if (state->scanner->value.v_char != ',') {
546 ini_append_error_expecting(state,
547 state->scanner, "','");
548 goto error;
549 }
550
551 state->expecting = INI_EXPECT_MAP_KEY;
552 goto success;
553 default:
554 abort();
555 }
556
557 error:
558 ret = -1;
559 goto end;
560
561 success:
562 if (value) {
563 if (bt_value_map_insert_entry(state->params,
564 state->last_map_key->str, value)) {
565 /* Only override return value on error */
566 ret = -1;
567 }
568 }
569
570 end:
571 BT_VALUE_PUT_REF_AND_RESET(value);
572 return ret;
573 }
574
575 /*
576 * Converts an INI-style argument to an equivalent map value object.
577 *
578 * Return value is owned by the caller.
579 */
580 BT_HIDDEN
581 bt_value *bt_param_parse(const char *arg, GString *ini_error)
582 {
583 /* Lexical scanner configuration */
584 GScannerConfig scanner_config = {
585 /* Skip whitespaces */
586 .cset_skip_characters = (gchar *) " \t\n",
587
588 /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
589 .cset_identifier_first = (gchar *)
590 G_CSET_a_2_z
591 "_"
592 G_CSET_A_2_Z,
593 .cset_identifier_nth = (gchar *)
594 G_CSET_a_2_z
595 "_0123456789-.:"
596 G_CSET_A_2_Z,
597
598 /* "hello" and "Hello" two different keys */
599 .case_sensitive = TRUE,
600
601 /* No comments */
602 .cpair_comment_single = NULL,
603 .skip_comment_multi = TRUE,
604 .skip_comment_single = TRUE,
605 .scan_comment_multi = FALSE,
606
607 /*
608 * Do scan identifiers, including 1-char identifiers,
609 * but NULL is a normal identifier.
610 */
611 .scan_identifier = TRUE,
612 .scan_identifier_1char = TRUE,
613 .scan_identifier_NULL = FALSE,
614
615 /*
616 * No specific symbols: null and boolean "symbols" are
617 * scanned as plain identifiers.
618 */
619 .scan_symbols = FALSE,
620 .symbol_2_token = FALSE,
621 .scope_0_fallback = FALSE,
622
623 /*
624 * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
625 * integers prefixed with "$".
626 */
627 .scan_binary = TRUE,
628 .scan_octal = TRUE,
629 .scan_float = TRUE,
630 .scan_hex = TRUE,
631 .scan_hex_dollar = FALSE,
632
633 /* Convert scanned numbers to integer tokens */
634 .numbers_2_int = TRUE,
635
636 /* Support both integers and floating point numbers */
637 .int_2_float = FALSE,
638
639 /* Scan integers as 64-bit signed integers */
640 .store_int64 = TRUE,
641
642 /* Only scan double-quoted strings */
643 .scan_string_sq = FALSE,
644 .scan_string_dq = TRUE,
645
646 /* Do not converter identifiers to string tokens */
647 .identifier_2_string = FALSE,
648
649 /* Scan characters as `G_TOKEN_CHAR` token */
650 .char_2_token = FALSE,
651 };
652 struct ini_parsing_state state = {
653 .scanner = NULL,
654 .params = NULL,
655 .expecting = INI_EXPECT_MAP_KEY,
656 .arg = arg,
657 .ini_error = ini_error,
658 };
659
660 BT_ASSERT(ini_error);
661 g_string_assign(ini_error, "");
662 state.params = bt_value_map_create();
663 if (!state.params) {
664 ini_append_oom_error(ini_error);
665 goto error;
666 }
667
668 state.scanner = g_scanner_new(&scanner_config);
669 if (!state.scanner) {
670 ini_append_oom_error(ini_error);
671 goto error;
672 }
673
674 state.last_map_key = g_string_new(NULL);
675 if (!state.last_map_key) {
676 ini_append_oom_error(ini_error);
677 goto error;
678 }
679
680 /* Let the scan begin */
681 g_scanner_input_text(state.scanner, arg, strlen(arg));
682
683 while (true) {
684 int ret = ini_handle_state(&state);
685
686 if (ret < 0) {
687 /* Error */
688 goto error;
689 } else if (ret > 0) {
690 /* Done */
691 break;
692 }
693 }
694
695 goto end;
696
697 error:
698 BT_VALUE_PUT_REF_AND_RESET(state.params);
699
700 end:
701 if (state.scanner) {
702 g_scanner_destroy(state.scanner);
703 }
704
705 if (state.last_map_key) {
706 g_string_free(state.last_map_key, TRUE);
707 }
708
709 return state.params;
710 }
This page took 0.043499 seconds and 5 git commands to generate.