2 * Copyright 2016-2019 Philippe Proulx <pproulx@efficios.com>
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:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
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
26 #include "common/assert.h"
30 #include <babeltrace2/babeltrace.h>
31 #include "common/common.h"
33 #include <sys/types.h>
34 #include "babeltrace2-cfg.h"
35 #include "babeltrace2-cfg-cli-args.h"
36 #include "babeltrace2-cfg-cli-args-connect.h"
38 /* INI-style parsing FSM states */
39 enum ini_parsing_fsm_state
{
40 /* Expect a map key (identifier) */
43 /* Expect an equal character (`=`) */
49 /* Expect a comma character (`,`) */
53 /* INI-style parsing state variables */
54 struct ini_parsing_state
{
55 /* Lexical scanner (owned by this) */
58 /* Output map value object being filled (owned by this) */
61 /* Next expected FSM state */
62 enum ini_parsing_fsm_state expecting
;
64 /* Last decoded map key (owned by this) */
65 GString
*last_map_key
;
67 /* Complete INI-style string to parse */
70 /* Error buffer (weak) */
75 * Appends an "expecting token" error to the INI-style parsing state's
79 void ini_append_error_expecting(struct ini_parsing_state
*state
,
80 GScanner
*scanner
, const char *expecting
)
85 g_string_append_printf(state
->ini_error
, "Expecting %s:\n", expecting
);
87 /* Only append error if there's one line */
88 if (strchr(state
->arg
, '\n') || strlen(state
->arg
) == 0) {
92 g_string_append_printf(state
->ini_error
, "\n %s\n", state
->arg
);
93 pos
= g_scanner_cur_position(scanner
) + 4;
95 if (!g_scanner_eof(scanner
)) {
99 for (i
= 0; i
< pos
; ++i
) {
100 g_string_append_printf(state
->ini_error
, " ");
103 g_string_append_printf(state
->ini_error
, "^\n\n");
107 void ini_append_oom_error(GString
*error
)
110 g_string_append(error
, "Out of memory\n");
114 * Parses the next token as an unsigned integer.
117 bt_value
*ini_parse_uint(struct ini_parsing_state
*state
)
119 bt_value
*value
= NULL
;
120 GTokenType token_type
= g_scanner_get_next_token(state
->scanner
);
122 if (token_type
!= G_TOKEN_INT
) {
123 ini_append_error_expecting(state
, state
->scanner
,
128 value
= bt_value_integer_unsigned_create_init(
129 state
->scanner
->value
.v_int64
);
136 * Parses the next token as a number and returns its negation.
139 bt_value
*ini_parse_neg_number(struct ini_parsing_state
*state
)
141 bt_value
*value
= NULL
;
142 GTokenType token_type
= g_scanner_get_next_token(state
->scanner
);
144 switch (token_type
) {
147 /* Negative integer */
148 uint64_t int_val
= state
->scanner
->value
.v_int64
;
150 if (int_val
> (((uint64_t) INT64_MAX
) + 1)) {
151 g_string_append_printf(state
->ini_error
,
152 "Integer value -%" PRIu64
" is outside the range of a 64-bit signed integer\n",
155 value
= bt_value_integer_signed_create_init(
156 -((int64_t) int_val
));
162 /* Negative floating point number */
163 value
= bt_value_real_create_init(
164 -state
->scanner
->value
.v_float
);
167 ini_append_error_expecting(state
, state
->scanner
, "value");
174 static bt_value
*ini_parse_value(struct ini_parsing_state
*state
);
177 * Parses the current and following tokens as an array. Arrays are
178 * formatted as an opening `[`, a list of comma-separated values, and a
179 * closing `]`. For convenience, this function supports an optional
180 * trailing comma after the last value.
182 * The current token of the parser must be the opening square bracket
183 * (`[`) of the array.
186 bt_value
*ini_parse_array(struct ini_parsing_state
*state
)
188 bt_value
*array_value
;
189 GTokenType token_type
;
191 /* The `[` character must have already been ingested */
192 BT_ASSERT(g_scanner_cur_token(state
->scanner
) == G_TOKEN_CHAR
);
193 BT_ASSERT(g_scanner_cur_value(state
->scanner
).v_char
== '[');
195 array_value
= bt_value_array_create ();
197 ini_append_oom_error(state
->ini_error
);
201 token_type
= g_scanner_get_next_token(state
->scanner
);
203 /* While the current token is not a `]` */
204 while (!(token_type
== G_TOKEN_CHAR
&&
205 g_scanner_cur_value(state
->scanner
).v_char
== ']')) {
206 bt_value
*item_value
;
207 bt_value_array_append_element_status append_status
;
209 /* Parse the item... */
210 item_value
= ini_parse_value(state
);
215 /* ... and add it to the result array */
216 append_status
= bt_value_array_append_element(array_value
,
218 BT_VALUE_PUT_REF_AND_RESET(item_value
);
219 if (append_status
< 0) {
224 * Ingest the token following the value. It should be
225 * either a comma or closing square bracket.
227 token_type
= g_scanner_get_next_token(state
->scanner
);
228 if (token_type
== G_TOKEN_CHAR
&&
229 g_scanner_cur_value(state
->scanner
).v_char
== ',') {
231 * Ingest the token following the comma. If it
232 * happens to be a closing square bracket, exit
233 * the loop and we are done (we allow trailing
234 * commas). Otherwise, we are ready for the next
235 * ini_parse_value() call.
237 token_type
= g_scanner_get_next_token(state
->scanner
);
238 } else if (token_type
!= G_TOKEN_CHAR
||
239 g_scanner_cur_value(state
->scanner
).v_char
!= ']') {
240 ini_append_error_expecting(state
, state
->scanner
,
249 BT_VALUE_PUT_REF_AND_RESET(array_value
);
256 * Parses the current and following tokens as a map. Maps are
257 * formatted as an opening `{`, a list of comma-separated entries, and a
258 * closing `}`. And entry is a key (an unquoted string), an equal sign and
259 * a value. For convenience, this function supports an optional trailing comma
260 * after the last value.
262 * The current token of the parser must be the opening curly bracket
263 * (`{`) of the array.
266 bt_value
*ini_parse_map(struct ini_parsing_state
*state
)
269 GTokenType token_type
;
272 /* The `{` character must have already been ingested */
273 BT_ASSERT(g_scanner_cur_token(state
->scanner
) == G_TOKEN_CHAR
);
274 BT_ASSERT(g_scanner_cur_value(state
->scanner
).v_char
== '{');
276 map_value
= bt_value_map_create ();
278 ini_append_oom_error(state
->ini_error
);
282 token_type
= g_scanner_get_next_token(state
->scanner
);
284 /* While the current token is not a `}` */
285 while (!(token_type
== G_TOKEN_CHAR
&&
286 g_scanner_cur_value(state
->scanner
).v_char
== '}')) {
287 bt_value
*entry_value
;
288 bt_value_map_insert_entry_status insert_entry_status
;
290 /* Expect map key. */
291 if (token_type
!= G_TOKEN_IDENTIFIER
) {
292 ini_append_error_expecting(state
, state
->scanner
,
298 key
= g_strdup(g_scanner_cur_value(state
->scanner
).v_identifier
);
300 token_type
= g_scanner_get_next_token(state
->scanner
);
302 /* Expect equal sign. */
303 if (token_type
!= G_TOKEN_CHAR
||
304 g_scanner_cur_value(state
->scanner
).v_char
!= '=') {
305 ini_append_error_expecting(state
,
306 state
->scanner
, "'='");
310 token_type
= g_scanner_get_next_token(state
->scanner
);
312 /* Parse the entry value... */
313 entry_value
= ini_parse_value(state
);
318 /* ... and add it to the result map */
319 insert_entry_status
=
320 bt_value_map_insert_entry(map_value
, key
, entry_value
);
321 BT_VALUE_PUT_REF_AND_RESET(entry_value
);
322 if (insert_entry_status
!= BT_VALUE_MAP_INSERT_ENTRY_STATUS_OK
) {
327 * Ingest the token following the value. It should be
328 * either a comma or closing curly bracket.
330 token_type
= g_scanner_get_next_token(state
->scanner
);
331 if (token_type
== G_TOKEN_CHAR
&&
332 g_scanner_cur_value(state
->scanner
).v_char
== ',') {
334 * Ingest the token following the comma. If it
335 * happens to be a closing curly bracket, exit
336 * the loop and we are done (we allow trailing
337 * commas). Otherwise, we are ready for the next
338 * ini_parse_value() call.
340 token_type
= g_scanner_get_next_token(state
->scanner
);
341 } else if (token_type
!= G_TOKEN_CHAR
||
342 g_scanner_cur_value(state
->scanner
).v_char
!= '}') {
343 ini_append_error_expecting(state
, state
->scanner
,
351 BT_VALUE_PUT_REF_AND_RESET(map_value
);
360 * Parses the current token (and the following ones if needed) as a
361 * value, returning it as a `bt_value *`.
364 bt_value
*ini_parse_value(struct ini_parsing_state
*state
)
366 bt_value
*value
= NULL
;
367 GTokenType token_type
= state
->scanner
->token
;
369 switch (token_type
) {
371 if (state
->scanner
->value
.v_char
== '-') {
372 /* Negative number */
373 value
= ini_parse_neg_number(state
);
374 } else if (state
->scanner
->value
.v_char
== '+') {
375 /* Unsigned integer */
376 value
= ini_parse_uint(state
);
377 } else if (state
->scanner
->value
.v_char
== '[') {
379 value
= ini_parse_array(state
);
380 } else if (state
->scanner
->value
.v_char
== '{') {
382 value
= ini_parse_map(state
);
384 ini_append_error_expecting(state
, state
->scanner
, "value");
391 /* Positive, signed integer */
392 uint64_t int_val
= state
->scanner
->value
.v_int64
;
394 if (int_val
> INT64_MAX
) {
395 g_string_append_printf(state
->ini_error
,
396 "Integer value %" PRIu64
" is outside the range of a 64-bit signed integer\n",
400 value
= bt_value_integer_signed_create_init(
407 /* Positive floating point number */
408 value
= bt_value_real_create_init(state
->scanner
->value
.v_float
);
412 value
= bt_value_string_create_init(state
->scanner
->value
.v_string
);
414 case G_TOKEN_IDENTIFIER
:
417 * Using symbols would be appropriate here, but said
418 * symbols are allowed as map key, so it's easier to
419 * consider everything an identifier.
421 * If one of the known symbols is not recognized here,
422 * then fall back to creating a string value.
424 const char *id
= state
->scanner
->value
.v_identifier
;
426 if (strcmp(id
, "null") == 0 || strcmp(id
, "NULL") == 0 ||
427 strcmp(id
, "nul") == 0) {
428 value
= bt_value_null
;
429 bt_value_get_ref(value
);
430 } else if (strcmp(id
, "true") == 0 || strcmp(id
, "TRUE") == 0 ||
431 strcmp(id
, "yes") == 0 ||
432 strcmp(id
, "YES") == 0) {
433 value
= bt_value_bool_create_init(true);
434 } else if (strcmp(id
, "false") == 0 ||
435 strcmp(id
, "FALSE") == 0 ||
436 strcmp(id
, "no") == 0 ||
437 strcmp(id
, "NO") == 0) {
438 value
= bt_value_bool_create_init(false);
440 value
= bt_value_string_create_init(id
);
445 /* Unset return value variable will trigger the error */
446 ini_append_error_expecting(state
, state
->scanner
, "value");
455 * Handles the current state of the INI parser.
457 * Returns 0 to continue, 1 to end, or a negative value on error.
460 int ini_handle_state(struct ini_parsing_state
*state
)
463 GTokenType token_type
;
464 bt_value
*value
= NULL
;
466 token_type
= g_scanner_get_next_token(state
->scanner
);
467 if (token_type
== G_TOKEN_EOF
) {
468 if (state
->expecting
!= INI_EXPECT_COMMA
) {
469 switch (state
->expecting
) {
470 case INI_EXPECT_EQUAL
:
471 ini_append_error_expecting(state
,
472 state
->scanner
, "`=`");
474 case INI_EXPECT_VALUE
:
475 ini_append_error_expecting(state
,
476 state
->scanner
, "value");
478 case INI_EXPECT_MAP_KEY
:
479 ini_append_error_expecting(state
,
480 state
->scanner
, "unquoted map key");
493 switch (state
->expecting
) {
494 case INI_EXPECT_MAP_KEY
:
495 if (token_type
!= G_TOKEN_IDENTIFIER
) {
496 ini_append_error_expecting(state
, state
->scanner
,
501 g_string_assign(state
->last_map_key
,
502 state
->scanner
->value
.v_identifier
);
504 if (bt_value_map_has_entry(state
->params
,
505 state
->last_map_key
->str
)) {
506 g_string_append_printf(state
->ini_error
,
507 "Duplicate parameter key: `%s`\n",
508 state
->last_map_key
->str
);
512 state
->expecting
= INI_EXPECT_EQUAL
;
514 case INI_EXPECT_EQUAL
:
515 if (token_type
!= G_TOKEN_CHAR
) {
516 ini_append_error_expecting(state
,
517 state
->scanner
, "'='");
521 if (state
->scanner
->value
.v_char
!= '=') {
522 ini_append_error_expecting(state
,
523 state
->scanner
, "'='");
527 state
->expecting
= INI_EXPECT_VALUE
;
529 case INI_EXPECT_VALUE
:
531 value
= ini_parse_value(state
);
536 state
->expecting
= INI_EXPECT_COMMA
;
539 case INI_EXPECT_COMMA
:
540 if (token_type
!= G_TOKEN_CHAR
) {
541 ini_append_error_expecting(state
,
542 state
->scanner
, "','");
546 if (state
->scanner
->value
.v_char
!= ',') {
547 ini_append_error_expecting(state
,
548 state
->scanner
, "','");
552 state
->expecting
= INI_EXPECT_MAP_KEY
;
564 if (bt_value_map_insert_entry(state
->params
,
565 state
->last_map_key
->str
, value
)) {
566 /* Only override return value on error */
572 BT_VALUE_PUT_REF_AND_RESET(value
);
577 * Converts an INI-style argument to an equivalent map value object.
579 * Return value is owned by the caller.
582 bt_value
*cli_value_from_arg(const char *arg
, GString
*ini_error
)
584 /* Lexical scanner configuration */
585 GScannerConfig scanner_config
= {
586 /* Skip whitespaces */
587 .cset_skip_characters
= " \t\n",
589 /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
590 .cset_identifier_first
=
594 .cset_identifier_nth
=
599 /* "hello" and "Hello" two different keys */
600 .case_sensitive
= TRUE
,
603 .cpair_comment_single
= NULL
,
604 .skip_comment_multi
= TRUE
,
605 .skip_comment_single
= TRUE
,
606 .scan_comment_multi
= FALSE
,
609 * Do scan identifiers, including 1-char identifiers,
610 * but NULL is a normal identifier.
612 .scan_identifier
= TRUE
,
613 .scan_identifier_1char
= TRUE
,
614 .scan_identifier_NULL
= FALSE
,
617 * No specific symbols: null and boolean "symbols" are
618 * scanned as plain identifiers.
620 .scan_symbols
= FALSE
,
621 .symbol_2_token
= FALSE
,
622 .scope_0_fallback
= FALSE
,
625 * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
626 * integers prefixed with "$".
632 .scan_hex_dollar
= FALSE
,
634 /* Convert scanned numbers to integer tokens */
635 .numbers_2_int
= TRUE
,
637 /* Support both integers and floating point numbers */
638 .int_2_float
= FALSE
,
640 /* Scan integers as 64-bit signed integers */
643 /* Only scan double-quoted strings */
644 .scan_string_sq
= FALSE
,
645 .scan_string_dq
= TRUE
,
647 /* Do not converter identifiers to string tokens */
648 .identifier_2_string
= FALSE
,
650 /* Scan characters as `G_TOKEN_CHAR` token */
651 .char_2_token
= FALSE
,
653 struct ini_parsing_state state
= {
656 .expecting
= INI_EXPECT_MAP_KEY
,
658 .ini_error
= ini_error
,
661 BT_ASSERT(ini_error
);
662 g_string_assign(ini_error
, "");
663 state
.params
= bt_value_map_create();
665 ini_append_oom_error(ini_error
);
669 state
.scanner
= g_scanner_new(&scanner_config
);
670 if (!state
.scanner
) {
671 ini_append_oom_error(ini_error
);
675 state
.last_map_key
= g_string_new(NULL
);
676 if (!state
.last_map_key
) {
677 ini_append_oom_error(ini_error
);
681 /* Let the scan begin */
682 g_scanner_input_text(state
.scanner
, arg
, strlen(arg
));
685 int ret
= ini_handle_state(&state
);
690 } else if (ret
> 0) {
699 BT_VALUE_PUT_REF_AND_RESET(state
.params
);
703 g_scanner_destroy(state
.scanner
);
706 if (state
.last_map_key
) {
707 g_string_free(state
.last_map_key
, TRUE
);