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