bt2: do not require sink component's _graph_is_configured() method
[babeltrace.git] / src / cli / babeltrace2-cfg-cli-params-arg.c
CommitLineData
8bd04432
PP
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 <popt.h>
33#include <glib.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"
39
40/* INI-style parsing FSM states */
41enum ini_parsing_fsm_state {
42 /* Expect a map key (identifier) */
43 INI_EXPECT_MAP_KEY,
44
45 /* Expect an equal character (`=`) */
46 INI_EXPECT_EQUAL,
47
48 /* Expect a value */
49 INI_EXPECT_VALUE,
50
51 /* Expect a comma character (`,`) */
52 INI_EXPECT_COMMA,
53};
54
55/* INI-style parsing state variables */
56struct ini_parsing_state {
57 /* Lexical scanner (owned by this) */
58 GScanner *scanner;
59
60 /* Output map value object being filled (owned by this) */
61 bt_value *params;
62
63 /* Next expected FSM state */
64 enum ini_parsing_fsm_state expecting;
65
66 /* Last decoded map key (owned by this) */
67 GString *last_map_key;
68
69 /* Complete INI-style string to parse */
70 const char *arg;
71
72 /* Error buffer (weak) */
73 GString *ini_error;
74};
75
76/*
77 * Appends an "expecting token" error to the INI-style parsing state's
78 * error buffer.
79 */
80static
81void ini_append_error_expecting(struct ini_parsing_state *state,
82 GScanner *scanner, const char *expecting)
83{
84 size_t i;
85 size_t pos;
86
87 g_string_append_printf(state->ini_error, "Expecting %s:\n", expecting);
88
89 /* Only append error if there's one line */
90 if (strchr(state->arg, '\n') != NULL || strlen(state->arg) == 0) {
91 return;
92 }
93
94 g_string_append_printf(state->ini_error, "\n %s\n", state->arg);
95 pos = g_scanner_cur_position(scanner) + 4;
96
97 if (!g_scanner_eof(scanner)) {
98 pos--;
99 }
100
101 for (i = 0; i < pos; ++i) {
102 g_string_append_printf(state->ini_error, " ");
103 }
104
105 g_string_append_printf(state->ini_error, "^\n\n");
106}
107
108static
109void ini_append_oom_error(GString *error)
110{
111 BT_ASSERT(error);
112 g_string_append(error, "Out of memory\n");
113}
114
115/*
116 * Parses the next token as an unsigned integer.
117 */
118static
119bt_value *ini_parse_uint(struct ini_parsing_state *state)
120{
121 bt_value *value = NULL;
122 GTokenType token_type = g_scanner_get_next_token(state->scanner);
123
124 if (token_type != G_TOKEN_INT) {
125 ini_append_error_expecting(state, state->scanner,
126 "integer value");
127 goto end;
128 }
129
9c08c816 130 value = bt_value_integer_unsigned_create_init(
8bd04432
PP
131 state->scanner->value.v_int64);
132
133end:
134 return value;
135}
136
137/*
138 * Parses the next token as a number and returns its negation.
139 */
140static
141bt_value *ini_parse_neg_number(struct ini_parsing_state *state)
142{
143 bt_value *value = NULL;
144 GTokenType token_type = g_scanner_get_next_token(state->scanner);
145
146 switch (token_type) {
147 case G_TOKEN_INT:
148 {
149 /* Negative integer */
150 uint64_t int_val = state->scanner->value.v_int64;
151
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",
155 int_val);
156 } else {
9c08c816 157 value = bt_value_integer_signed_create_init(
8bd04432
PP
158 -((int64_t) int_val));
159 }
160
161 break;
162 }
163 case G_TOKEN_FLOAT:
164 /* Negative floating point number */
165 value = bt_value_real_create_init(
166 -state->scanner->value.v_float);
167 break;
168 default:
169 ini_append_error_expecting(state, state->scanner, "value");
170 break;
171 }
172
173 return value;
174}
175
176static bt_value *ini_parse_value(struct ini_parsing_state *state);
177
178/*
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.
183 *
184 * The current token of the parser must be the opening square bracket
185 * (`[`) of the array.
186 */
187static
188bt_value *ini_parse_array(struct ini_parsing_state *state)
189{
190 bt_value *array_value;
191 GTokenType token_type;
192
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 == '[');
196
197 array_value = bt_value_array_create ();
198 if (!array_value) {
199 ini_append_oom_error(state->ini_error);
200 goto error;
201 }
202
203 token_type = g_scanner_get_next_token(state->scanner);
204
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;
210
211 /* Parse the item... */
212 item_value = ini_parse_value(state);
213 if (!item_value) {
214 goto error;
215 }
216
217 /* ... and add it to the result array */
218 append_status = bt_value_array_append_element(array_value,
219 item_value);
220 BT_VALUE_PUT_REF_AND_RESET(item_value);
221 if (append_status < 0) {
222 goto error;
223 }
224
225 /*
226 * Ingest the token following the value. It should be
227 * either a comma or closing square bracket.
228 */
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 == ',') {
232 /*
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.
238 */
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,
243 "`,` or `]`");
244 goto error;
245 }
246 }
247
248 goto end;
249
250error:
251 BT_VALUE_PUT_REF_AND_RESET(array_value);
252
253end:
254 return array_value;
255}
256
257/*
258 * Parses the current token (and the following ones if needed) as a
259 * value, returning it as a `bt_value *`.
260 */
261static
262bt_value *ini_parse_value(struct ini_parsing_state *state)
263{
264 bt_value *value = NULL;
265 GTokenType token_type = state->scanner->token;
266
267 switch (token_type) {
268 case G_TOKEN_CHAR:
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 == '[') {
276 /* Array */
277 value = ini_parse_array(state);
278 } else {
279 ini_append_error_expecting(state, state->scanner, "value");
280 goto end;
281 }
282
283 break;
284 case G_TOKEN_INT:
285 {
286 /* Positive, signed integer */
287 uint64_t int_val = state->scanner->value.v_int64;
288
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",
292 int_val);
293 goto end;
294 } else {
9c08c816 295 value = bt_value_integer_signed_create_init(
8bd04432
PP
296 (int64_t) int_val);
297 }
298
299 break;
300 }
301 case G_TOKEN_FLOAT:
302 /* Positive floating point number */
303 value = bt_value_real_create_init(state->scanner->value.v_float);
304 break;
305 case G_TOKEN_STRING:
306 /* Quoted string */
307 value = bt_value_string_create_init(state->scanner->value.v_string);
308 break;
309 case G_TOKEN_IDENTIFIER:
310 {
311 /*
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.
315 *
316 * If one of the known symbols is not recognized here,
317 * then fall back to creating a string value.
318 */
319 const char *id = state->scanner->value.v_identifier;
320
2242b43d
PP
321 if (strcmp(id, "null") == 0 || strcmp(id, "NULL") == 0 ||
322 strcmp(id, "nul") == 0) {
8bd04432 323 value = bt_value_null;
2242b43d
PP
324 } else if (strcmp(id, "true") == 0 || strcmp(id, "TRUE") == 0 ||
325 strcmp(id, "yes") == 0 ||
326 strcmp(id, "YES") == 0) {
8bd04432 327 value = bt_value_bool_create_init(true);
2242b43d
PP
328 } else if (strcmp(id, "false") == 0 ||
329 strcmp(id, "FALSE") == 0 ||
330 strcmp(id, "no") == 0 ||
331 strcmp(id, "NO") == 0) {
8bd04432
PP
332 value = bt_value_bool_create_init(false);
333 } else {
334 value = bt_value_string_create_init(id);
335 }
336 break;
337 }
338 default:
339 /* Unset return value variable will trigger the error */
340 ini_append_error_expecting(state, state->scanner, "value");
341 break;
342 }
343
344end:
345 return value;
346}
347
348/*
349 * Handles the current state of the INI parser.
350 *
351 * Returns 0 to continue, 1 to end, or a negative value on error.
352 */
353static
354int ini_handle_state(struct ini_parsing_state *state)
355{
356 int ret = 0;
357 GTokenType token_type;
358 bt_value *value = NULL;
359
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, "`=`");
367 break;
368 case INI_EXPECT_VALUE:
369 ini_append_error_expecting(state,
370 state->scanner, "value");
371 break;
372 case INI_EXPECT_MAP_KEY:
373 ini_append_error_expecting(state,
374 state->scanner, "unquoted map key");
375 break;
376 default:
377 break;
378 }
379 goto error;
380 }
381
382 /* We're done! */
383 ret = 1;
384 goto success;
385 }
386
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,
391 "unquoted map key");
392 goto error;
393 }
394
395 g_string_assign(state->last_map_key,
396 state->scanner->value.v_identifier);
397
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);
403 goto error;
404 }
405
406 state->expecting = INI_EXPECT_EQUAL;
407 goto success;
408 case INI_EXPECT_EQUAL:
409 if (token_type != G_TOKEN_CHAR) {
410 ini_append_error_expecting(state,
411 state->scanner, "'='");
412 goto error;
413 }
414
415 if (state->scanner->value.v_char != '=') {
416 ini_append_error_expecting(state,
417 state->scanner, "'='");
418 goto error;
419 }
420
421 state->expecting = INI_EXPECT_VALUE;
422 goto success;
423 case INI_EXPECT_VALUE:
424 {
425 value = ini_parse_value(state);
426 if (!value) {
427 goto error;
428 }
429
430 state->expecting = INI_EXPECT_COMMA;
431 goto success;
432 }
433 case INI_EXPECT_COMMA:
434 if (token_type != G_TOKEN_CHAR) {
435 ini_append_error_expecting(state,
436 state->scanner, "','");
437 goto error;
438 }
439
440 if (state->scanner->value.v_char != ',') {
441 ini_append_error_expecting(state,
442 state->scanner, "','");
443 goto error;
444 }
445
446 state->expecting = INI_EXPECT_MAP_KEY;
447 goto success;
448 default:
449 abort();
450 }
451
452error:
453 ret = -1;
454 goto end;
455
456success:
457 if (value) {
458 if (bt_value_map_insert_entry(state->params,
459 state->last_map_key->str, value)) {
460 /* Only override return value on error */
461 ret = -1;
462 }
463 }
464
465end:
466 BT_VALUE_PUT_REF_AND_RESET(value);
467 return ret;
468}
469
470/*
471 * Converts an INI-style argument to an equivalent map value object.
472 *
473 * Return value is owned by the caller.
474 */
475BT_HIDDEN
476bt_value *cli_value_from_arg(const char *arg, GString *ini_error)
477{
478 /* Lexical scanner configuration */
479 GScannerConfig scanner_config = {
480 /* Skip whitespaces */
481 .cset_skip_characters = " \t\n",
482
483 /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
484 .cset_identifier_first =
485 G_CSET_a_2_z
486 "_"
487 G_CSET_A_2_Z,
488 .cset_identifier_nth =
489 G_CSET_a_2_z
490 "_0123456789-.:"
491 G_CSET_A_2_Z,
492
493 /* "hello" and "Hello" two different keys */
494 .case_sensitive = TRUE,
495
496 /* No comments */
497 .cpair_comment_single = NULL,
498 .skip_comment_multi = TRUE,
499 .skip_comment_single = TRUE,
500 .scan_comment_multi = FALSE,
501
502 /*
503 * Do scan identifiers, including 1-char identifiers,
504 * but NULL is a normal identifier.
505 */
506 .scan_identifier = TRUE,
507 .scan_identifier_1char = TRUE,
508 .scan_identifier_NULL = FALSE,
509
510 /*
511 * No specific symbols: null and boolean "symbols" are
512 * scanned as plain identifiers.
513 */
514 .scan_symbols = FALSE,
515 .symbol_2_token = FALSE,
516 .scope_0_fallback = FALSE,
517
518 /*
519 * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
520 * integers prefixed with "$".
521 */
522 .scan_binary = TRUE,
523 .scan_octal = TRUE,
524 .scan_float = TRUE,
525 .scan_hex = TRUE,
526 .scan_hex_dollar = FALSE,
527
528 /* Convert scanned numbers to integer tokens */
529 .numbers_2_int = TRUE,
530
531 /* Support both integers and floating point numbers */
532 .int_2_float = FALSE,
533
534 /* Scan integers as 64-bit signed integers */
535 .store_int64 = TRUE,
536
537 /* Only scan double-quoted strings */
538 .scan_string_sq = FALSE,
539 .scan_string_dq = TRUE,
540
541 /* Do not converter identifiers to string tokens */
542 .identifier_2_string = FALSE,
543
544 /* Scan characters as `G_TOKEN_CHAR` token */
545 .char_2_token = FALSE,
546 };
547 struct ini_parsing_state state = {
548 .scanner = NULL,
549 .params = NULL,
550 .expecting = INI_EXPECT_MAP_KEY,
551 .arg = arg,
552 .ini_error = ini_error,
553 };
554
555 BT_ASSERT(ini_error);
556 g_string_assign(ini_error, "");
557 state.params = bt_value_map_create();
558 if (!state.params) {
559 ini_append_oom_error(ini_error);
560 goto error;
561 }
562
563 state.scanner = g_scanner_new(&scanner_config);
564 if (!state.scanner) {
565 ini_append_oom_error(ini_error);
566 goto error;
567 }
568
569 state.last_map_key = g_string_new(NULL);
570 if (!state.last_map_key) {
571 ini_append_oom_error(ini_error);
572 goto error;
573 }
574
575 /* Let the scan begin */
576 g_scanner_input_text(state.scanner, arg, strlen(arg));
577
578 while (true) {
579 int ret = ini_handle_state(&state);
580
581 if (ret < 0) {
582 /* Error */
583 goto error;
584 } else if (ret > 0) {
585 /* Done */
586 break;
587 }
588 }
589
590 goto end;
591
592error:
593 BT_VALUE_PUT_REF_AND_RESET(state.params);
594
595end:
596 if (state.scanner) {
597 g_scanner_destroy(state.scanner);
598 }
599
600 if (state.last_map_key) {
601 g_string_free(state.last_map_key, TRUE);
602 }
603
604 return state.params;
605}
This page took 0.046582 seconds and 4 git commands to generate.