cli: move `--params` option's format parsing to its own file
[babeltrace.git] / src / cli / babeltrace2-cfg-cli-args.c
index ca7694e01b8702a92bae0579d744dcf7e49738d8..8da792e131e27622dc47f5b26546939beed3055b 100644 (file)
@@ -40,6 +40,7 @@
 #include "babeltrace2-cfg.h"
 #include "babeltrace2-cfg-cli-args.h"
 #include "babeltrace2-cfg-cli-args-connect.h"
+#include "babeltrace2-cfg-cli-params-arg.h"
 #include "common/version.h"
 
 /*
@@ -154,534 +155,6 @@ void print_err_oom(void)
        printf_err("Out of memory\n");
 }
 
-/*
- * Appends an "expecting token" error to the INI-style parsing state's
- * error buffer.
- */
-static
-void ini_append_error_expecting(struct ini_parsing_state *state,
-               GScanner *scanner, const char *expecting)
-{
-       size_t i;
-       size_t pos;
-
-       g_string_append_printf(state->ini_error, "Expecting %s:\n", expecting);
-
-       /* Only print error if there's one line */
-       if (strchr(state->arg, '\n') != NULL || strlen(state->arg) == 0) {
-               return;
-       }
-
-       g_string_append_printf(state->ini_error, "\n    %s\n", state->arg);
-       pos = g_scanner_cur_position(scanner) + 4;
-
-       if (!g_scanner_eof(scanner)) {
-               pos--;
-       }
-
-       for (i = 0; i < pos; ++i) {
-               g_string_append_printf(state->ini_error, " ");
-       }
-
-       g_string_append_printf(state->ini_error, "^\n\n");
-}
-
-/* Parse the next token as an unsigned integer. */
-static
-bt_value *ini_parse_uint(struct ini_parsing_state *state)
-{
-       bt_value *value = NULL;
-       GTokenType token_type = g_scanner_get_next_token(state->scanner);
-
-       if (token_type != G_TOKEN_INT) {
-               ini_append_error_expecting(state, state->scanner,
-                       "integer value");
-               goto end;
-       }
-
-       value = bt_value_unsigned_integer_create_init(
-               state->scanner->value.v_int64);
-
-end:
-       return value;
-}
-
-/* Parse the next token as a number and return its negation. */
-static
-bt_value *ini_parse_neg_number(struct ini_parsing_state *state)
-{
-       bt_value *value = NULL;
-       GTokenType token_type = g_scanner_get_next_token(state->scanner);
-
-       switch (token_type) {
-       case G_TOKEN_INT:
-       {
-               /* Negative integer */
-               uint64_t int_val = state->scanner->value.v_int64;
-
-               if (int_val > (((uint64_t) INT64_MAX) + 1)) {
-                       g_string_append_printf(state->ini_error,
-                               "Integer value -%" PRIu64 " is outside the range of a 64-bit signed integer\n",
-                               int_val);
-               } else {
-                       value = bt_value_signed_integer_create_init(
-                               -((int64_t) int_val));
-               }
-
-               break;
-       }
-       case G_TOKEN_FLOAT:
-               /* Negative floating point number */
-               value = bt_value_real_create_init(-state->scanner->value.v_float);
-               break;
-       default:
-               ini_append_error_expecting(state, state->scanner, "value");
-               break;
-       }
-
-       return value;
-}
-
-static bt_value *ini_parse_value(struct ini_parsing_state *state);
-
-/*
- * Parse the current and following tokens as an array.  Arrays are formatted as
- * an opening `[`, a list of comma-separated values and a closing `]`.  For
- * convenience we support an optional trailing comma, after the last value.
- *
- * The current token of the parser must be the opening square bracket of the
- * array.
- */
-static
-bt_value *ini_parse_array(struct ini_parsing_state *state)
-{
-       /* The [ character must have already been ingested. */
-       BT_ASSERT(g_scanner_cur_token(state->scanner) == G_TOKEN_CHAR);
-       BT_ASSERT(g_scanner_cur_value(state->scanner).v_char == '[');
-
-       bt_value *array_value;
-       GTokenType token_type;
-
-       array_value = bt_value_array_create ();
-       token_type = g_scanner_get_next_token(state->scanner);
-
-       /* While the current token is not a ]... */
-       while (!(token_type == G_TOKEN_CHAR && g_scanner_cur_value(state->scanner).v_char == ']')) {
-               /* Parse the item... */
-               bt_value *item_value;
-               bt_value_array_append_element_status append_status;
-
-               item_value = ini_parse_value(state);
-               if (!item_value) {
-                       goto error;
-               }
-
-               /* ... and add it to the result array. */
-               append_status = bt_value_array_append_element(array_value,
-                       item_value);
-               BT_VALUE_PUT_REF_AND_RESET(item_value);
-               if (append_status < 0) {
-                       goto error;
-               }
-
-               /*
-                * Ingest the token following the value, it should be either a
-                * comma or closing square brace.
-                */
-               token_type = g_scanner_get_next_token(state->scanner);
-
-               if (token_type == G_TOKEN_CHAR && g_scanner_cur_value(state->scanner).v_char == ',') {
-                       /*
-                        * Ingest the token following the comma.  If it happens
-                        * to be a closing square bracket, we'll exit the loop
-                        * and we are done (we allow trailing commas).
-                        * Otherwise, we are ready for the next ini_parse_value call.
-                        */
-                       token_type = g_scanner_get_next_token(state->scanner);
-               } else if (token_type != G_TOKEN_CHAR || g_scanner_cur_value(state->scanner).v_char != ']') {
-                       ini_append_error_expecting(state, state->scanner, ", or ]");
-                       goto error;
-               }
-       }
-
-       goto end;
-
-error:
-       BT_VALUE_PUT_REF_AND_RESET(array_value);
-
-end:
-       return array_value;
-}
-
-/*
- * Parse the current token (and the following ones if needed) as a value, return
- * it as a bt_value.
- */
-static
-bt_value *ini_parse_value(struct ini_parsing_state *state)
-{
-       bt_value *value = NULL;
-       GTokenType token_type = state->scanner->token;
-
-       switch (token_type) {
-       case G_TOKEN_CHAR:
-               if (state->scanner->value.v_char == '-') {
-                       /* Negative number */
-                       value = ini_parse_neg_number(state);
-               } else if (state->scanner->value.v_char == '+') {
-                       /* Unsigned integer */
-                       value = ini_parse_uint(state);
-               } else if (state->scanner->value.v_char == '[') {
-                       /* Array */
-                       value = ini_parse_array(state);
-               } else {
-                       ini_append_error_expecting(state, state->scanner, "value");
-               }
-               break;
-       case G_TOKEN_INT:
-       {
-               /* Positive, signed integer */
-               uint64_t int_val = state->scanner->value.v_int64;
-
-               if (int_val > INT64_MAX) {
-                       g_string_append_printf(state->ini_error,
-                               "Integer value %" PRIu64 " is outside the range of a 64-bit signed integer\n",
-                               int_val);
-               } else {
-                       value = bt_value_signed_integer_create_init(
-                               (int64_t) int_val);
-               }
-               break;
-       }
-       case G_TOKEN_FLOAT:
-               /* Positive floating point number */
-               value = bt_value_real_create_init(state->scanner->value.v_float);
-               break;
-       case G_TOKEN_STRING:
-               /* Quoted string */
-               value = bt_value_string_create_init(state->scanner->value.v_string);
-               break;
-       case G_TOKEN_IDENTIFIER:
-       {
-               /*
-                * Using symbols would be appropriate here,
-                * but said symbols are allowed as map key,
-                * so it's easier to consider everything an
-                * identifier.
-                *
-                * If one of the known symbols is not
-                * recognized here, then fall back to creating
-                * a string value.
-                */
-               const char *id = state->scanner->value.v_identifier;
-
-               if (!strcmp(id, "null") || !strcmp(id, "NULL") ||
-                               !strcmp(id, "nul")) {
-                       value = bt_value_null;
-               } else if (!strcmp(id, "true") || !strcmp(id, "TRUE") ||
-                               !strcmp(id, "yes") ||
-                               !strcmp(id, "YES")) {
-                       value = bt_value_bool_create_init(true);
-               } else if (!strcmp(id, "false") ||
-                               !strcmp(id, "FALSE") ||
-                               !strcmp(id, "no") ||
-                               !strcmp(id, "NO")) {
-                       value = bt_value_bool_create_init(false);
-               } else {
-                       value = bt_value_string_create_init(id);
-               }
-               break;
-       }
-       default:
-               /* Unset value variable will trigger the error */
-               ini_append_error_expecting(state, state->scanner, "value");
-               break;
-       }
-
-       return value;
-}
-
-static
-int ini_handle_state(struct ini_parsing_state *state)
-{
-       int ret = 0;
-       GTokenType token_type;
-       bt_value *value = NULL;
-
-       token_type = g_scanner_get_next_token(state->scanner);
-       if (token_type == G_TOKEN_EOF) {
-               if (state->expecting != INI_EXPECT_COMMA) {
-                       switch (state->expecting) {
-                       case INI_EXPECT_EQUAL:
-                               ini_append_error_expecting(state,
-                                       state->scanner, "'='");
-                               break;
-                       case INI_EXPECT_VALUE:
-                               ini_append_error_expecting(state,
-                                       state->scanner, "value");
-                               break;
-                       case INI_EXPECT_MAP_KEY:
-                               ini_append_error_expecting(state,
-                                       state->scanner, "unquoted map key");
-                               break;
-                       default:
-                               break;
-                       }
-                       goto error;
-               }
-
-               /* We're done! */
-               ret = 1;
-               goto success;
-       }
-
-       switch (state->expecting) {
-       case INI_EXPECT_MAP_KEY:
-               if (token_type != G_TOKEN_IDENTIFIER) {
-                       ini_append_error_expecting(state, state->scanner,
-                               "unquoted map key");
-                       goto error;
-               }
-
-               free(state->last_map_key);
-               state->last_map_key =
-                       strdup(state->scanner->value.v_identifier);
-               if (!state->last_map_key) {
-                       g_string_append(state->ini_error,
-                               "Out of memory\n");
-                       goto error;
-               }
-
-               if (bt_value_map_has_entry(state->params,
-                                          state->last_map_key)) {
-                       g_string_append_printf(state->ini_error,
-                               "Duplicate parameter key: `%s`\n",
-                               state->last_map_key);
-                       goto error;
-               }
-
-               state->expecting = INI_EXPECT_EQUAL;
-               goto success;
-       case INI_EXPECT_EQUAL:
-               if (token_type != G_TOKEN_CHAR) {
-                       ini_append_error_expecting(state,
-                               state->scanner, "'='");
-                       goto error;
-               }
-
-               if (state->scanner->value.v_char != '=') {
-                       ini_append_error_expecting(state,
-                               state->scanner, "'='");
-                       goto error;
-               }
-
-               state->expecting = INI_EXPECT_VALUE;
-               goto success;
-       case INI_EXPECT_VALUE:
-       {
-               value = ini_parse_value(state);
-               if (!value) {
-                       goto error;
-               }
-
-               state->expecting = INI_EXPECT_COMMA;
-               goto success;
-       }
-       case INI_EXPECT_COMMA:
-               if (token_type != G_TOKEN_CHAR) {
-                       ini_append_error_expecting(state,
-                               state->scanner, "','");
-                       goto error;
-               }
-
-               if (state->scanner->value.v_char != ',') {
-                       ini_append_error_expecting(state,
-                               state->scanner, "','");
-                       goto error;
-               }
-
-               state->expecting = INI_EXPECT_MAP_KEY;
-               goto success;
-       default:
-               abort();
-       }
-
-error:
-       ret = -1;
-       goto end;
-
-success:
-       if (value) {
-               if (bt_value_map_insert_entry(state->params,
-                               state->last_map_key, value)) {
-                       /* Only override return value on error */
-                       ret = -1;
-               }
-       }
-
-end:
-       BT_VALUE_PUT_REF_AND_RESET(value);
-       return ret;
-}
-
-/*
- * Converts an INI-style argument to an equivalent map value object.
- *
- * Return value is owned by the caller.
- */
-static
-bt_value *bt_value_from_ini(const char *arg,
-               GString *ini_error)
-{
-       /* Lexical scanner configuration */
-       GScannerConfig scanner_config = {
-               /* Skip whitespaces */
-               .cset_skip_characters = " \t\n",
-
-               /* Identifier syntax is: [a-zA-Z_][a-zA-Z0-9_.:-]* */
-               .cset_identifier_first =
-                       G_CSET_a_2_z
-                       "_"
-                       G_CSET_A_2_Z,
-               .cset_identifier_nth =
-                       G_CSET_a_2_z
-                       "_0123456789-.:"
-                       G_CSET_A_2_Z,
-
-               /* "hello" and "Hello" two different keys */
-               .case_sensitive = TRUE,
-
-               /* No comments */
-               .cpair_comment_single = NULL,
-               .skip_comment_multi = TRUE,
-               .skip_comment_single = TRUE,
-               .scan_comment_multi = FALSE,
-
-               /*
-                * Do scan identifiers, including 1-char identifiers,
-                * but NULL is a normal identifier.
-                */
-               .scan_identifier = TRUE,
-               .scan_identifier_1char = TRUE,
-               .scan_identifier_NULL = FALSE,
-
-               /*
-                * No specific symbols: null and boolean "symbols" are
-                * scanned as plain identifiers.
-                */
-               .scan_symbols = FALSE,
-               .symbol_2_token = FALSE,
-               .scope_0_fallback = FALSE,
-
-               /*
-                * Scan "0b"-, "0"-, and "0x"-prefixed integers, but not
-                * integers prefixed with "$".
-                */
-               .scan_binary = TRUE,
-               .scan_octal = TRUE,
-               .scan_float = TRUE,
-               .scan_hex = TRUE,
-               .scan_hex_dollar = FALSE,
-
-               /* Convert scanned numbers to integer tokens */
-               .numbers_2_int = TRUE,
-
-               /* Support both integers and floating-point numbers */
-               .int_2_float = FALSE,
-
-               /* Scan integers as 64-bit signed integers */
-               .store_int64 = TRUE,
-
-               /* Only scan double-quoted strings */
-               .scan_string_sq = FALSE,
-               .scan_string_dq = TRUE,
-
-               /* Do not converter identifiers to string tokens */
-               .identifier_2_string = FALSE,
-
-               /* Scan characters as G_TOKEN_CHAR token */
-               .char_2_token = FALSE,
-       };
-       struct ini_parsing_state state = {
-               .scanner = NULL,
-               .params = NULL,
-               .expecting = INI_EXPECT_MAP_KEY,
-               .arg = arg,
-               .ini_error = ini_error,
-       };
-
-       state.params = bt_value_map_create();
-       if (!state.params) {
-               goto error;
-       }
-
-       state.scanner = g_scanner_new(&scanner_config);
-       if (!state.scanner) {
-               goto error;
-       }
-
-       /* Let the scan begin */
-       g_scanner_input_text(state.scanner, arg, strlen(arg));
-
-       while (true) {
-               int ret = ini_handle_state(&state);
-
-               if (ret < 0) {
-                       /* Error */
-                       goto error;
-               } else if (ret > 0) {
-                       /* Done */
-                       break;
-               }
-       }
-
-       goto end;
-
-error:
-       BT_VALUE_PUT_REF_AND_RESET(state.params);
-
-end:
-       if (state.scanner) {
-               g_scanner_destroy(state.scanner);
-       }
-
-       free(state.last_map_key);
-       return state.params;
-}
-
-/*
- * Returns the parameters map value object from a command-line
- * parameter option's argument.
- *
- * Return value is owned by the caller.
- */
-static
-bt_value *bt_value_from_arg(const char *arg)
-{
-       bt_value *params = NULL;
-       GString *ini_error = NULL;
-
-       ini_error = g_string_new(NULL);
-       if (!ini_error) {
-               print_err_oom();
-               goto end;
-       }
-
-       /* Try INI-style parsing */
-       params = bt_value_from_ini(arg, ini_error);
-       if (!params) {
-               printf_err("%s", ini_error->str);
-               goto end;
-       }
-
-end:
-       if (ini_error) {
-               g_string_free(ini_error, TRUE);
-       }
-
-       return params;
-}
-
 /*
  * Returns the plugin name, component class name, component class type,
  * and component name from a command-line --component option's argument.
@@ -2106,6 +1579,7 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[],
        struct bt_config *cfg = NULL;
        const char *leftover;
        bt_value *params;
+       GString *error_str = NULL;
 
        params = bt_value_null;
        bt_value_get_ref(bt_value_null);
@@ -2116,6 +1590,12 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[],
                goto error;
        }
 
+       error_str = g_string_new(NULL);
+       if (!error_str) {
+               print_err_oom();
+               goto error;
+       }
+
        cfg->omit_system_plugin_path = force_omit_system_plugin_path;
        cfg->omit_home_plugin_path = force_omit_home_plugin_path;
        ret = append_env_var_plugin_paths(cfg->plugin_paths);
@@ -2152,10 +1632,10 @@ struct bt_config *bt_config_query_from_args(int argc, const char *argv[],
                case OPT_PARAMS:
                {
                        bt_value_put_ref(params);
-                       params = bt_value_from_arg(arg);
+                       params = cli_value_from_arg(arg, error_str);
                        if (!params) {
                                printf_err("Invalid format for --params option's argument:\n    %s\n",
-                                       arg);
+                                       error_str->str);
                                goto error;
                        }
                        break;
@@ -2243,6 +1723,10 @@ end:
                poptFreeContext(pc);
        }
 
+       if (error_str) {
+               g_string_free(error_str, TRUE);
+       }
+
        bt_value_put_ref(params);
        free(arg);
        return cfg;
@@ -2491,6 +1975,7 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[],
        char error_buf[256] = { 0 };
        long retry_duration = -1;
        bt_value_map_extend_status extend_status;
+       GString *error_str = NULL;
        struct poptOption run_long_options[] = {
                { "base-params", 'b', POPT_ARG_STRING, NULL, OPT_BASE_PARAMS, NULL, NULL },
                { "component", 'c', POPT_ARG_STRING, NULL, OPT_COMPONENT, NULL, NULL },
@@ -2509,6 +1994,12 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[],
 
        *retcode = 0;
 
+       error_str = g_string_new(NULL);
+       if (!error_str) {
+               print_err_oom();
+               goto error;
+       }
+
        if (argc <= 1) {
                print_run_usage(stdout);
                *retcode = -1;
@@ -2630,10 +2121,10 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[],
                                goto error;
                        }
 
-                       params = bt_value_from_arg(arg);
+                       params = cli_value_from_arg(arg, error_str);
                        if (!params) {
                                printf_err("Invalid format for --params option's argument:\n    %s\n",
-                                       arg);
+                                       error_str->str);
                                goto error;
                        }
 
@@ -2675,12 +2166,11 @@ struct bt_config *bt_config_run_from_args(int argc, const char *argv[],
                        break;
                case OPT_BASE_PARAMS:
                {
-                       bt_value *params =
-                               bt_value_from_arg(arg);
+                       bt_value *params = cli_value_from_arg(arg, error_str);
 
                        if (!params) {
                                printf_err("Invalid format for --base-params option's argument:\n    %s\n",
-                                       arg);
+                                       error_str->str);
                                goto error;
                        }
 
@@ -2783,6 +2273,10 @@ end:
                poptFreeContext(pc);
        }
 
+       if (error_str) {
+               g_string_free(error_str, TRUE);
+       }
+
        free(arg);
        BT_OBJECT_PUT_REF_AND_RESET(cur_cfg_comp);
        BT_VALUE_PUT_REF_AND_RESET(cur_base_params);
This page took 0.030957 seconds and 4 git commands to generate.