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"
34 #include <sys/types.h>
35 #include "babeltrace2-cfg.h"
36 #include "babeltrace2-cfg-cli-args.h"
37 #include "babeltrace2-cfg-cli-args-connect.h"
38 #include "common/version.h"
40 /* INI-style parsing FSM states */
41 enum ini_parsing_fsm_state
{
42 /* Expect a map key (identifier) */
45 /* Expect an equal character (`=`) */
51 /* Expect a comma character (`,`) */
55 /* INI-style parsing state variables */
56 struct ini_parsing_state
{
57 /* Lexical scanner (owned by this) */
60 /* Output map value object being filled (owned by this) */
63 /* Next expected FSM state */
64 enum ini_parsing_fsm_state expecting
;
66 /* Last decoded map key (owned by this) */
67 GString
*last_map_key
;
69 /* Complete INI-style string to parse */
72 /* Error buffer (weak) */
77 * Appends an "expecting token" error to the INI-style parsing state's
81 void ini_append_error_expecting(struct ini_parsing_state
*state
,
82 GScanner
*scanner
, const char *expecting
)
87 g_string_append_printf(state
->ini_error
, "Expecting %s:\n", expecting
);
89 /* Only append error if there's one line */
90 if (strchr(state
->arg
, '\n') != NULL
|| strlen(state
->arg
) == 0) {
94 g_string_append_printf(state
->ini_error
, "\n %s\n", state
->arg
);
95 pos
= g_scanner_cur_position(scanner
) + 4;
97 if (!g_scanner_eof(scanner
)) {
101 for (i
= 0; i
< pos
; ++i
) {
102 g_string_append_printf(state
->ini_error
, " ");
105 g_string_append_printf(state
->ini_error
, "^\n\n");
109 void ini_append_oom_error(GString
*error
)
112 g_string_append(error
, "Out of memory\n");
116 * Parses the next token as an unsigned integer.
119 bt_value
*ini_parse_uint(struct ini_parsing_state
*state
)
121 bt_value
*value
= NULL
;
122 GTokenType token_type
= g_scanner_get_next_token(state
->scanner
);
124 if (token_type
!= G_TOKEN_INT
) {
125 ini_append_error_expecting(state
, state
->scanner
,
130 value
= bt_value_integer_unsigned_create_init(
131 state
->scanner
->value
.v_int64
);
138 * Parses the next token as a number and returns its negation.
141 bt_value
*ini_parse_neg_number(struct ini_parsing_state
*state
)
143 bt_value
*value
= NULL
;
144 GTokenType token_type
= g_scanner_get_next_token(state
->scanner
);
146 switch (token_type
) {
149 /* Negative integer */
150 uint64_t int_val
= state
->scanner
->value
.v_int64
;
152 if (int_val
> (((uint64_t) INT64_MAX
) + 1)) {
153 g_string_append_printf(state
->ini_error
,
154 "Integer value -%" PRIu64
" is outside the range of a 64-bit signed integer\n",
157 value
= bt_value_integer_signed_create_init(
158 -((int64_t) int_val
));
164 /* Negative floating point number */
165 value
= bt_value_real_create_init(
166 -state
->scanner
->value
.v_float
);
169 ini_append_error_expecting(state
, state
->scanner
, "value");
176 static bt_value
*ini_parse_value(struct ini_parsing_state
*state
);
179 * Parses the current and following tokens as an array. Arrays are
180 * formatted as an opening `[`, a list of comma-separated values, and a
181 * closing `]`. For convenience, this function supports an optional
182 * trailing comma after the last value.
184 * The current token of the parser must be the opening square bracket
185 * (`[`) of the array.
188 bt_value
*ini_parse_array(struct ini_parsing_state
*state
)
190 bt_value
*array_value
;
191 GTokenType token_type
;
193 /* The `[` character must have already been ingested */
194 BT_ASSERT(g_scanner_cur_token(state
->scanner
) == G_TOKEN_CHAR
);
195 BT_ASSERT(g_scanner_cur_value(state
->scanner
).v_char
== '[');
197 array_value
= bt_value_array_create ();
199 ini_append_oom_error(state
->ini_error
);
203 token_type
= g_scanner_get_next_token(state
->scanner
);
205 /* While the current token is not a `]` */
206 while (!(token_type
== G_TOKEN_CHAR
&&
207 g_scanner_cur_value(state
->scanner
).v_char
== ']')) {
208 bt_value
*item_value
;
209 bt_value_array_append_element_status append_status
;
211 /* Parse the item... */
212 item_value
= ini_parse_value(state
);
217 /* ... and add it to the result array */
218 append_status
= bt_value_array_append_element(array_value
,
220 BT_VALUE_PUT_REF_AND_RESET(item_value
);
221 if (append_status
< 0) {
226 * Ingest the token following the value. It should be
227 * either a comma or closing square bracket.
229 token_type
= g_scanner_get_next_token(state
->scanner
);
230 if (token_type
== G_TOKEN_CHAR
&&
231 g_scanner_cur_value(state
->scanner
).v_char
== ',') {
233 * Ingest the token following the comma. If it
234 * happens to be a closing square bracket, exit
235 * the loop and we are done (we allow trailing
236 * commas). Otherwise, we are ready for the next
237 * ini_parse_value() call.
239 token_type
= g_scanner_get_next_token(state
->scanner
);
240 } else if (token_type
!= G_TOKEN_CHAR
||
241 g_scanner_cur_value(state
->scanner
).v_char
!= ']') {
242 ini_append_error_expecting(state
, state
->scanner
,
251 BT_VALUE_PUT_REF_AND_RESET(array_value
);
258 * Parses the current token (and the following ones if needed) as a
259 * value, returning it as a `bt_value *`.
262 bt_value
*ini_parse_value(struct ini_parsing_state
*state
)
264 bt_value
*value
= NULL
;
265 GTokenType token_type
= state
->scanner
->token
;
267 switch (token_type
) {
269 if (state
->scanner
->value
.v_char
== '-') {
270 /* Negative number */
271 value
= ini_parse_neg_number(state
);
272 } else if (state
->scanner
->value
.v_char
== '+') {
273 /* Unsigned integer */
274 value
= ini_parse_uint(state
);
275 } else if (state
->scanner
->value
.v_char
== '[') {
277 value
= ini_parse_array(state
);
279 ini_append_error_expecting(state
, state
->scanner
, "value");
286 /* Positive, signed integer */
287 uint64_t int_val
= state
->scanner
->value
.v_int64
;
289 if (int_val
> INT64_MAX
) {
290 g_string_append_printf(state
->ini_error
,
291 "Integer value %" PRIu64
" is outside the range of a 64-bit signed integer\n",
295 value
= bt_value_integer_signed_create_init(
302 /* Positive floating point number */
303 value
= bt_value_real_create_init(state
->scanner
->value
.v_float
);
307 value
= bt_value_string_create_init(state
->scanner
->value
.v_string
);
309 case G_TOKEN_IDENTIFIER
:
312 * Using symbols would be appropriate here, but said
313 * symbols are allowed as map key, so it's easier to
314 * consider everything an identifier.
316 * If one of the known symbols is not recognized here,
317 * then fall back to creating a string value.
319 const char *id
= state
->scanner
->value
.v_identifier
;
321 if (strcmp(id
, "null") == 0 || strcmp(id
, "NULL") == 0 ||
322 strcmp(id
, "nul") == 0) {
323 value
= bt_value_null
;
324 } else if (strcmp(id
, "true") == 0 || strcmp(id
, "TRUE") == 0 ||
325 strcmp(id
, "yes") == 0 ||
326 strcmp(id
, "YES") == 0) {
327 value
= bt_value_bool_create_init(true);
328 } else if (strcmp(id
, "false") == 0 ||
329 strcmp(id
, "FALSE") == 0 ||
330 strcmp(id
, "no") == 0 ||
331 strcmp(id
, "NO") == 0) {
332 value
= bt_value_bool_create_init(false);
334 value
= bt_value_string_create_init(id
);
339 /* Unset return value variable will trigger the error */
340 ini_append_error_expecting(state
, state
->scanner
, "value");
349 * Handles the current state of the INI parser.
351 * Returns 0 to continue, 1 to end, or a negative value on error.
354 int ini_handle_state(struct ini_parsing_state
*state
)
357 GTokenType token_type
;
358 bt_value
*value
= NULL
;
360 token_type
= g_scanner_get_next_token(state
->scanner
);
361 if (token_type
== G_TOKEN_EOF
) {
362 if (state
->expecting
!= INI_EXPECT_COMMA
) {
363 switch (state
->expecting
) {
364 case INI_EXPECT_EQUAL
:
365 ini_append_error_expecting(state
,
366 state
->scanner
, "`=`");
368 case INI_EXPECT_VALUE
:
369 ini_append_error_expecting(state
,
370 state
->scanner
, "value");
372 case INI_EXPECT_MAP_KEY
:
373 ini_append_error_expecting(state
,
374 state
->scanner
, "unquoted map key");
387 switch (state
->expecting
) {
388 case INI_EXPECT_MAP_KEY
:
389 if (token_type
!= G_TOKEN_IDENTIFIER
) {
390 ini_append_error_expecting(state
, state
->scanner
,
395 g_string_assign(state
->last_map_key
,
396 state
->scanner
->value
.v_identifier
);
398 if (bt_value_map_has_entry(state
->params
,
399 state
->last_map_key
->str
)) {
400 g_string_append_printf(state
->ini_error
,
401 "Duplicate parameter key: `%s`\n",
402 state
->last_map_key
->str
);
406 state
->expecting
= INI_EXPECT_EQUAL
;
408 case INI_EXPECT_EQUAL
:
409 if (token_type
!= G_TOKEN_CHAR
) {
410 ini_append_error_expecting(state
,
411 state
->scanner
, "'='");
415 if (state
->scanner
->value
.v_char
!= '=') {
416 ini_append_error_expecting(state
,
417 state
->scanner
, "'='");
421 state
->expecting
= INI_EXPECT_VALUE
;
423 case INI_EXPECT_VALUE
:
425 value
= ini_parse_value(state
);
430 state
->expecting
= INI_EXPECT_COMMA
;
433 case INI_EXPECT_COMMA
:
434 if (token_type
!= G_TOKEN_CHAR
) {
435 ini_append_error_expecting(state
,
436 state
->scanner
, "','");
440 if (state
->scanner
->value
.v_char
!= ',') {
441 ini_append_error_expecting(state
,
442 state
->scanner
, "','");
446 state
->expecting
= INI_EXPECT_MAP_KEY
;
458 if (bt_value_map_insert_entry(state
->params
,
459 state
->last_map_key
->str
, value
)) {
460 /* Only override return value on error */
466 BT_VALUE_PUT_REF_AND_RESET(value
);
471 * Converts an INI-style argument to an equivalent map value object.
473 * Return value is owned by the caller.
476 bt_value
*cli_value_from_arg(const char *arg
, GString
*ini_error
)
478 /* Lexical scanner configuration */
479 GScannerConfig scanner_config
= {
480 /* Skip whitespaces */
481 .cset_skip_characters
= " \t\n",
483 /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
484 .cset_identifier_first
=
488 .cset_identifier_nth
=
493 /* "hello" and "Hello" two different keys */
494 .case_sensitive
= TRUE
,
497 .cpair_comment_single
= NULL
,
498 .skip_comment_multi
= TRUE
,
499 .skip_comment_single
= TRUE
,
500 .scan_comment_multi
= FALSE
,
503 * Do scan identifiers, including 1-char identifiers,
504 * but NULL is a normal identifier.
506 .scan_identifier
= TRUE
,
507 .scan_identifier_1char
= TRUE
,
508 .scan_identifier_NULL
= FALSE
,
511 * No specific symbols: null and boolean "symbols" are
512 * scanned as plain identifiers.
514 .scan_symbols
= FALSE
,
515 .symbol_2_token
= FALSE
,
516 .scope_0_fallback
= FALSE
,
519 * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
520 * integers prefixed with "$".
526 .scan_hex_dollar
= FALSE
,
528 /* Convert scanned numbers to integer tokens */
529 .numbers_2_int
= TRUE
,
531 /* Support both integers and floating point numbers */
532 .int_2_float
= FALSE
,
534 /* Scan integers as 64-bit signed integers */
537 /* Only scan double-quoted strings */
538 .scan_string_sq
= FALSE
,
539 .scan_string_dq
= TRUE
,
541 /* Do not converter identifiers to string tokens */
542 .identifier_2_string
= FALSE
,
544 /* Scan characters as `G_TOKEN_CHAR` token */
545 .char_2_token
= FALSE
,
547 struct ini_parsing_state state
= {
550 .expecting
= INI_EXPECT_MAP_KEY
,
552 .ini_error
= ini_error
,
555 BT_ASSERT(ini_error
);
556 g_string_assign(ini_error
, "");
557 state
.params
= bt_value_map_create();
559 ini_append_oom_error(ini_error
);
563 state
.scanner
= g_scanner_new(&scanner_config
);
564 if (!state
.scanner
) {
565 ini_append_oom_error(ini_error
);
569 state
.last_map_key
= g_string_new(NULL
);
570 if (!state
.last_map_key
) {
571 ini_append_oom_error(ini_error
);
575 /* Let the scan begin */
576 g_scanner_input_text(state
.scanner
, arg
, strlen(arg
));
579 int ret
= ini_handle_state(&state
);
584 } else if (ret
> 0) {
593 BT_VALUE_PUT_REF_AND_RESET(state
.params
);
597 g_scanner_destroy(state
.scanner
);
600 if (state
.last_map_key
) {
601 g_string_free(state
.last_map_key
, TRUE
);