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