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