From 08b8b2f735e41ccdfe5cd8f9f10bb333e94d9449 Mon Sep 17 00:00:00 2001 From: Lei Liu Date: Thu, 12 Jan 2023 10:35:49 +0800 Subject: [PATCH] Add yaml tag full support. Signed-off-by: Lei Liu --- rcl_yaml_param_parser/src/parse.c | 380 +++++++++++++----- .../test/correct_config.yaml | 26 ++ .../test/test_parse_yaml.cpp | 76 ++++ 3 files changed, 386 insertions(+), 96 deletions(-) diff --git a/rcl_yaml_param_parser/src/parse.c b/rcl_yaml_param_parser/src/parse.c index 967d437d2..3eb2c5da8 100644 --- a/rcl_yaml_param_parser/src/parse.c +++ b/rcl_yaml_param_parser/src/parse.c @@ -67,6 +67,65 @@ RCL_YAML_PARAM_PARSER_LOCAL rcutils_ret_t _validate_name(const char * name, rcutils_allocator_t allocator); +/// +/// Check a tag whether it is valid +/// +/// \param tag the tag to check +/// \param line_num the line number error happened +/// \return RCUTILS_RET_OK if tag is valid, or +/// \return RCUTILS_RET_ERROR if tag is not valid +RCL_YAML_PARAM_PARSER_LOCAL +rcutils_ret_t +_validate_tag(const char * tag, uint32_t line_num); + +/// +/// Check a bool value whether it is valid +/// +/// \param value the bool value to check +/// \param val_type the value type +/// \param ret_val the converted value when value is valid +/// \return RCUTILS_RET_OK if value is valid, or +/// \return RCUTILS_RET_ERROR if value is not valid +RCL_YAML_PARAM_PARSER_LOCAL +rcutils_ret_t +_validate_bool_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator); + +/// +/// Check a int value whether it is valid +/// +/// \param value the int value to check +/// \param val_type the value type +/// \param ret_val the converted value when value is valid +/// \return RCUTILS_RET_OK if value is valid, or +/// \return RCUTILS_RET_ERROR if value is not valid +RCL_YAML_PARAM_PARSER_LOCAL +rcutils_ret_t +_validate_int_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator); + +/// +/// Check a float value whether it is valid +/// +/// \param value the float value to check +/// \param val_type the value type +/// \param ret_val the converted value when value is valid +/// \return RCUTILS_RET_OK if value is valid, or +/// \return RCUTILS_RET_ERROR if value is not valid +RCL_YAML_PARAM_PARSER_LOCAL +rcutils_ret_t +_validate_float_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator); + /// /// Determine the type of the value and return the converted value /// NOTE: Only canonical forms supported as of now @@ -78,10 +137,7 @@ void * get_value( data_types_t * val_type, const rcutils_allocator_t allocator) { - void * ret_val; - int64_t ival; - double dval; - char * endptr = NULL; + void * ret_val = NULL; RCUTILS_CHECK_ARGUMENT_FOR_NULL(value, NULL); RCUTILS_CHECK_ARGUMENT_FOR_NULL(val_type, NULL); @@ -94,49 +150,51 @@ void * get_value( return rcutils_strdup(value, allocator); } - /// Check if it is bool - if (style != YAML_SINGLE_QUOTED_SCALAR_STYLE && - style != YAML_DOUBLE_QUOTED_SCALAR_STYLE) - { - if ((0 == strcmp(value, "Y")) || - (0 == strcmp(value, "y")) || - (0 == strcmp(value, "yes")) || - (0 == strcmp(value, "Yes")) || - (0 == strcmp(value, "YES")) || - (0 == strcmp(value, "true")) || - (0 == strcmp(value, "True")) || - (0 == strcmp(value, "TRUE")) || - (0 == strcmp(value, "on")) || - (0 == strcmp(value, "On")) || - (0 == strcmp(value, "ON"))) + /// Check for yaml null tag + if (tag != NULL && strcmp(YAML_NULL_TAG, (char *)tag) == 0) { + *val_type = DATA_TYPE_STRING; + if ((0 == strcmp(value, "null")) || + (0 == strcmp(value, "Null")) || + (0 == strcmp(value, "none")) || + (0 == strcmp(value, "None"))) { - *val_type = DATA_TYPE_BOOL; - ret_val = allocator.zero_allocate(1U, sizeof(bool), allocator.state); - if (NULL == ret_val) { - return NULL; - } - *((bool *)ret_val) = true; + return rcutils_strdup("null", allocator); + } + return NULL; + } + + /// Check for yaml bool tag + if (tag != NULL && strcmp(YAML_BOOL_TAG, (char *)tag) == 0) { + if (_validate_bool_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { return ret_val; + } else { + return NULL; } + } - if ((0 == strcmp(value, "N")) || - (0 == strcmp(value, "n")) || - (0 == strcmp(value, "no")) || - (0 == strcmp(value, "No")) || - (0 == strcmp(value, "NO")) || - (0 == strcmp(value, "false")) || - (0 == strcmp(value, "False")) || - (0 == strcmp(value, "FALSE")) || - (0 == strcmp(value, "off")) || - (0 == strcmp(value, "Off")) || - (0 == strcmp(value, "OFF"))) - { - *val_type = DATA_TYPE_BOOL; - ret_val = allocator.zero_allocate(1U, sizeof(bool), allocator.state); - if (NULL == ret_val) { - return NULL; - } - *((bool *)ret_val) = false; + /// Check for yaml int tag + if (tag != NULL && strcmp(YAML_INT_TAG, (char *)tag) == 0) { + if (_validate_int_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { + return ret_val; + } else { + return NULL; + } + } + + /// Check for yaml float tag + if (tag != NULL && strcmp(YAML_FLOAT_TAG, (char *)tag) == 0) { + if (_validate_float_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { + return ret_val; + } else { + return NULL; + } + } + + /// Check if it is bool + if (style != YAML_SINGLE_QUOTED_SCALAR_STYLE && + style != YAML_DOUBLE_QUOTED_SCALAR_STYLE) + { + if (_validate_bool_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { return ret_val; } } @@ -145,20 +203,8 @@ void * get_value( if (style != YAML_SINGLE_QUOTED_SCALAR_STYLE && style != YAML_DOUBLE_QUOTED_SCALAR_STYLE) { - errno = 0; - ival = strtol(value, &endptr, 0); - if ((0 == errno) && (NULL != endptr)) { - if ((NULL != endptr) && (endptr != value)) { - if (('\0' != *value) && ('\0' == *endptr)) { - *val_type = DATA_TYPE_INT64; - ret_val = allocator.zero_allocate(1U, sizeof(int64_t), allocator.state); - if (NULL == ret_val) { - return NULL; - } - *((int64_t *)ret_val) = ival; - return ret_val; - } - } + if (_validate_int_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { + return ret_val; } } @@ -166,46 +212,9 @@ void * get_value( if (style != YAML_SINGLE_QUOTED_SCALAR_STYLE && style != YAML_DOUBLE_QUOTED_SCALAR_STYLE) { - errno = 0; - endptr = NULL; - const char * iter_ptr = NULL; - if ((0 == strcmp(value, ".nan")) || - (0 == strcmp(value, ".NaN")) || - (0 == strcmp(value, ".NAN")) || - (0 == strcmp(value, ".inf")) || - (0 == strcmp(value, ".Inf")) || - (0 == strcmp(value, ".INF")) || - (0 == strcmp(value, "+.inf")) || - (0 == strcmp(value, "+.Inf")) || - (0 == strcmp(value, "+.INF")) || - (0 == strcmp(value, "-.inf")) || - (0 == strcmp(value, "-.Inf")) || - (0 == strcmp(value, "-.INF"))) - { - for (iter_ptr = value; !isalpha(*iter_ptr); ) { - iter_ptr += 1; - } - dval = strtod(iter_ptr, &endptr); - if (*value == '-') { - dval = -dval; - } - } else { - dval = strtod(value, &endptr); - } - if ((0 == errno) && (NULL != endptr)) { - if ((NULL != endptr) && (endptr != value)) { - if (('\0' != *value) && ('\0' == *endptr)) { - *val_type = DATA_TYPE_DOUBLE; - ret_val = allocator.zero_allocate(1U, sizeof(double), allocator.state); - if (NULL == ret_val) { - return NULL; - } - *((double *)ret_val) = dval; - return ret_val; - } - } + if (_validate_float_value(value, val_type, &ret_val, allocator) == RCUTILS_RET_OK) { + return ret_val; } - errno = 0; } /// It is a string @@ -585,6 +594,166 @@ _validate_name(const char * name, rcutils_allocator_t allocator) return ret; } +rcutils_ret_t +_validate_tag(const char * tag, uint32_t line_num) +{ + if (tag == NULL) { + return RCUTILS_RET_ERROR; + } + + if ((0 == strcmp(tag, YAML_NULL_TAG)) || + (0 == strcmp(tag, YAML_BOOL_TAG)) || + (0 == strcmp(tag, YAML_STR_TAG)) || + (0 == strcmp(tag, YAML_INT_TAG)) || + (0 == strcmp(tag, YAML_FLOAT_TAG)) || + (0 == strcmp(tag, YAML_TIMESTAMP_TAG)) || + (0 == strcmp(tag, YAML_SEQ_TAG)) || + (0 == strcmp(tag, YAML_MAP_TAG))) + { + return RCUTILS_RET_OK; + } + + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Unsupported tag %s at line %d", (char *)tag, line_num); + return RCUTILS_RET_ERROR; +} + +rcutils_ret_t +_validate_bool_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator) +{ + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + if ((0 == strcmp(value, "Y")) || + (0 == strcmp(value, "y")) || + (0 == strcmp(value, "yes")) || + (0 == strcmp(value, "Yes")) || + (0 == strcmp(value, "YES")) || + (0 == strcmp(value, "true")) || + (0 == strcmp(value, "True")) || + (0 == strcmp(value, "TRUE")) || + (0 == strcmp(value, "on")) || + (0 == strcmp(value, "On")) || + (0 == strcmp(value, "ON"))) + { + *val_type = DATA_TYPE_BOOL; + *ret_val = allocator.zero_allocate(1U, sizeof(bool), allocator.state); + if (NULL != *ret_val) { + *((bool *)*ret_val) = true; + } + ret = RCUTILS_RET_OK; + goto final; + } + + if ((0 == strcmp(value, "N")) || + (0 == strcmp(value, "n")) || + (0 == strcmp(value, "no")) || + (0 == strcmp(value, "No")) || + (0 == strcmp(value, "NO")) || + (0 == strcmp(value, "false")) || + (0 == strcmp(value, "False")) || + (0 == strcmp(value, "FALSE")) || + (0 == strcmp(value, "off")) || + (0 == strcmp(value, "Off")) || + (0 == strcmp(value, "OFF"))) + { + *val_type = DATA_TYPE_BOOL; + *ret_val = allocator.zero_allocate(1U, sizeof(bool), allocator.state); + if (NULL != *ret_val) { + *((bool *)*ret_val) = false; + } + ret = RCUTILS_RET_OK; + goto final; + } + +final: + return ret; +} + +rcutils_ret_t +_validate_int_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator) +{ + errno = 0; + int64_t ival; + char * endptr = NULL; + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + ival = strtol(value, &endptr, 0); + if ((0 == errno) && (NULL != endptr)) { + if ((NULL != endptr) && (endptr != value)) { + if (('\0' != *value) && ('\0' == *endptr)) { + *val_type = DATA_TYPE_INT64; + *ret_val = allocator.zero_allocate(1U, sizeof(int64_t), allocator.state); + if (NULL != *ret_val) { + *((int64_t *)*ret_val) = ival; + } + ret = RCUTILS_RET_OK; + } + } + } + + return ret; +} + +rcutils_ret_t +_validate_float_value( + const char * const value, + data_types_t * val_type, + void ** ret_val, + const rcutils_allocator_t allocator) +{ + errno = 0; + double dval; + char * endptr = NULL; + const char * iter_ptr = NULL; + rcutils_ret_t ret = RCUTILS_RET_ERROR; + + if ((0 == strcmp(value, ".nan")) || + (0 == strcmp(value, ".NaN")) || + (0 == strcmp(value, ".NAN")) || + (0 == strcmp(value, ".inf")) || + (0 == strcmp(value, ".Inf")) || + (0 == strcmp(value, ".INF")) || + (0 == strcmp(value, "+.inf")) || + (0 == strcmp(value, "+.Inf")) || + (0 == strcmp(value, "+.INF")) || + (0 == strcmp(value, "-.inf")) || + (0 == strcmp(value, "-.Inf")) || + (0 == strcmp(value, "-.INF"))) + { + for (iter_ptr = value; !isalpha(*iter_ptr); ) { + iter_ptr += 1; + } + dval = strtod(iter_ptr, &endptr); + if (*value == '-') { + dval = -dval; + } + } else { + dval = strtod(value, &endptr); + } + if ((0 == errno) && (NULL != endptr)) { + if ((NULL != endptr) && (endptr != value)) { + if (('\0' != *value) && ('\0' == *endptr)) { + *val_type = DATA_TYPE_DOUBLE; + *ret_val = allocator.zero_allocate(1U, sizeof(double), allocator.state); + if (NULL != *ret_val) { + *((double *)*ret_val) = dval; + } + ret = RCUTILS_RET_OK; + } + } + } + + return ret; +} + /// /// Parse the key part of the pair /// @@ -780,6 +949,13 @@ rcutils_ret_t parse_file_events( break; } line_num = ((uint32_t)(event.start_mark.line) + 1U); + const yaml_char_t * const tag = event.data.scalar.tag; + if (tag != NULL) { + ret = _validate_tag((char *)tag, line_num); + if (RCUTILS_RET_OK != ret) { + break; + } + } switch (event.type) { case YAML_STREAM_END_EVENT: done_parsing = 1; @@ -826,6 +1002,12 @@ rcutils_ret_t parse_file_events( } break; case YAML_SEQUENCE_START_EVENT: + if (tag != NULL && strcmp(YAML_SEQ_TAG, (char *)tag) != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Sequences cannot be used with tag %s at line %d", (char *)tag, line_num); + ret = RCUTILS_RET_ERROR; + break; + } if (is_key) { RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( "Sequences cannot be key at line %d", line_num); @@ -846,6 +1028,12 @@ rcutils_ret_t parse_file_events( is_key = true; break; case YAML_MAPPING_START_EVENT: + if (tag != NULL && strcmp(YAML_MAP_TAG, (char *)tag) != 0) { + RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING( + "Maps cannot be used with tag %s at line %d", (char *)tag, line_num); + ret = RCUTILS_RET_ERROR; + break; + } map_depth++; is_new_map = true; is_key = true; diff --git a/rcl_yaml_param_parser/test/correct_config.yaml b/rcl_yaml_param_parser/test/correct_config.yaml index 502b3e3d9..54661364d 100644 --- a/rcl_yaml_param_parser/test/correct_config.yaml +++ b/rcl_yaml_param_parser/test/correct_config.yaml @@ -54,3 +54,29 @@ string_tag: string_bool: !!str yes string_int: !!str 1234 string_double: !!str 12.34 +bool_tag: + ros__parameters: + bool_true: !!bool true + bool_yes: !!bool yes + bool_on: !!bool on + bool_false: !!bool false + bool_no: !!bool no + bool_off: !!bool off +null_tag: + ros__parameters: + null_null: !!null null + null_none: !!null none +int_tag: + ros__parameters: + int_pos: !!int 10 + int_zero: !!int 0 + int_neg: !!int -10 +float_tag: + ros__parameters: + float_pos: !!float 1.1 + float_zero: !!float 0.0 + float_neg: !!float -1.122 + float_nan: !!float .nan + float_inf: !!float .inf + float_infn: !!float +.inf + float_infp: !!float -.inf diff --git a/rcl_yaml_param_parser/test/test_parse_yaml.cpp b/rcl_yaml_param_parser/test/test_parse_yaml.cpp index 9a357c7c2..85ce3809c 100644 --- a/rcl_yaml_param_parser/test/test_parse_yaml.cpp +++ b/rcl_yaml_param_parser/test/test_parse_yaml.cpp @@ -207,6 +207,82 @@ TEST(test_parser, correct_syntax) { ASSERT_TRUE(NULL != param_value->string_value); EXPECT_STREQ("12.34", param_value->string_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_true", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_TRUE(*param_value->bool_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_yes", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_TRUE(*param_value->bool_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_on", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_TRUE(*param_value->bool_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_false", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_FALSE(*param_value->bool_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_no", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_FALSE(*param_value->bool_value); + param_value = rcl_yaml_node_struct_get("bool_tag", "bool_off", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->bool_value); + EXPECT_FALSE(*param_value->bool_value); + + param_value = rcl_yaml_node_struct_get("null_tag", "null_null", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->string_value); + EXPECT_STREQ("null", param_value->string_value); + param_value = rcl_yaml_node_struct_get("null_tag", "null_none", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->string_value); + EXPECT_STREQ("null", param_value->string_value); + + param_value = rcl_yaml_node_struct_get("int_tag", "int_pos", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->integer_value); + EXPECT_EQ(10, *param_value->integer_value); + param_value = rcl_yaml_node_struct_get("int_tag", "int_zero", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->integer_value); + EXPECT_EQ(0, *param_value->integer_value); + param_value = rcl_yaml_node_struct_get("int_tag", "int_neg", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->integer_value); + EXPECT_EQ(-10, *param_value->integer_value); + + param_value = rcl_yaml_node_struct_get("float_tag", "float_pos", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(1.1, *param_value->double_value); + param_value = rcl_yaml_node_struct_get("float_tag", "float_zero", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(0.0, *param_value->double_value); + param_value = rcl_yaml_node_struct_get("float_tag", "float_neg", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(-1.122, *param_value->double_value); + param_value = rcl_yaml_node_struct_get("float_tag", "float_nan", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_TRUE(std::isnan(*param_value->double_value)); + param_value = rcl_yaml_node_struct_get("float_tag", "float_inf", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(INFINITY, *param_value->double_value); + param_value = rcl_yaml_node_struct_get("float_tag", "float_infn", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(INFINITY, *param_value->double_value); + param_value = rcl_yaml_node_struct_get("float_tag", "float_infp", params); + ASSERT_TRUE(NULL != param_value) << rcutils_get_error_string().str; + ASSERT_TRUE(NULL != param_value->double_value); + EXPECT_DOUBLE_EQ(-INFINITY, *param_value->double_value); + rcl_yaml_node_struct_print(params); } }