diff --git a/rosidl_generator_cpp/CMakeLists.txt b/rosidl_generator_cpp/CMakeLists.txt index 042a79c51..654d4c0f9 100644 --- a/rosidl_generator_cpp/CMakeLists.txt +++ b/rosidl_generator_cpp/CMakeLists.txt @@ -102,6 +102,11 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} ) endif() + if(MSVC) + # https://developercommunity.visualstudio.com/content/problem/919371/c2017-illegal-escape-sequence-when-using-in-a-raw.html + set_source_files_properties( + test/test_traits.cpp PROPERTIES COMPILE_FLAGS "/Zc:preprocessor") + endif() ament_add_gtest(test_traits test/test_traits.cpp) if(TARGET test_traits) add_dependencies(test_traits ${PROJECT_NAME}) diff --git a/rosidl_generator_cpp/resource/idl__traits.hpp.em b/rosidl_generator_cpp/resource/idl__traits.hpp.em index 47e2635b4..ef6752225 100644 --- a/rosidl_generator_cpp/resource/idl__traits.hpp.em +++ b/rosidl_generator_cpp/resource/idl__traits.hpp.em @@ -27,6 +27,7 @@ include_directives = set() #include "@(include_base)__struct.hpp" #include #include +#include #include @####################################################################### diff --git a/rosidl_generator_cpp/resource/msg__traits.hpp.em b/rosidl_generator_cpp/resource/msg__traits.hpp.em index b2dcfe26e..e63f80a17 100644 --- a/rosidl_generator_cpp/resource/msg__traits.hpp.em +++ b/rosidl_generator_cpp/resource/msg__traits.hpp.em @@ -5,7 +5,9 @@ from rosidl_parser.definition import ACTION_GOAL_SUFFIX from rosidl_parser.definition import ACTION_RESULT_SUFFIX from rosidl_parser.definition import Array from rosidl_parser.definition import AbstractGenericString +from rosidl_parser.definition import BasicType from rosidl_parser.definition import BoundedSequence +from rosidl_parser.definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME from rosidl_parser.definition import NamespacedType from rosidl_parser.definition import AbstractSequence from rosidl_parser.definition import UnboundedSequence @@ -61,6 +63,79 @@ for member in message.structure.members: namespace rosidl_generator_traits { +inline void to_yaml( + const @(message_typename) & msg, + std::ostream & out, size_t indentation = 0) +{ +@[if len(message.structure.members) == 1 and message.structure.members[0].name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@ + (void)msg; + (void)indentation; + out << "null\n"; +@[else]@ +@[ for i, member in enumerate(message.structure.members)]@ +@[ if i]@ + +@[ end if]@ + // member: @(member.name) + { + if (indentation > 0) { + out << std::string(indentation, ' '); + } +@[ if isinstance(member.type, BasicType)]@ + out << "@(member.name): "; +@[ if member.type.typename in ('octet', 'char', 'wchar')]@ + character_value_to_yaml(msg.@(member.name), out); +@[ else]@ + value_to_yaml(msg.@(member.name), out); +@[ end if]@ + out << "\n"; +@[ elif isinstance(member.type, AbstractGenericString)]@ + out << "@(member.name): "; + value_to_yaml(msg.@(member.name), out); + out << "\n"; +@[ elif isinstance(member.type, NamespacedType)]@ + out << "@(member.name):\n"; + to_yaml(msg.@(member.name), out, indentation + 2); +@[ elif isinstance(member.type, (AbstractSequence, Array))]@ + if (msg.@(member.name).size() == 0) { + out << "@(member.name): []\n"; + } else { + out << "@(member.name):\n"; + for (auto item : msg.@(member.name)) { + if (indentation > 0) { + out << std::string(indentation, ' '); + } +@[ if isinstance(member.type.value_type, BasicType)]@ + out << "- "; +@[ if member.type.value_type.typename in ('octet', 'char', 'wchar')]@ + character_value_to_yaml(item, out); +@[ else]@ + value_to_yaml(item, out); +@[ end if]@ + out << "\n"; +@[ elif isinstance(member.type.value_type, AbstractGenericString)]@ + out << "- "; + value_to_yaml(item, out); + out << "\n"; +@[ elif isinstance(member.type.value_type, NamespacedType)]@ + out << "-\n"; + to_yaml(item, out, indentation + 2); +@[ end if]@ + } + } +@[ end if]@ + } +@[ end for]@ +@[end if]@ +} // NOLINT(readability/fn_size) + +inline std::string to_yaml(const @(message_typename) & msg) +{ + std::ostringstream out; + to_yaml(msg, out); + return out.str(); +} + template<> inline const char * data_type<@(message_typename)>() { diff --git a/rosidl_generator_cpp/test/test_traits.cpp b/rosidl_generator_cpp/test/test_traits.cpp index 1b05e3081..de46877f7 100644 --- a/rosidl_generator_cpp/test/test_traits.cpp +++ b/rosidl_generator_cpp/test/test_traits.cpp @@ -1,3 +1,4 @@ +// NOLINT: This file starts with a BOM since it contain non-ASCII characters // Copyright 2019 Open Source Robotics Foundation, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,14 +14,247 @@ // limitations under the License. #include +#include + +// the idl file is commented out in the test_interface_files package +// #include "rosidl_generator_cpp/idl/idl_only_types.hpp" +#include "rosidl_generator_cpp/msg/defaults.hpp" #include "rosidl_generator_cpp/msg/empty.hpp" +#include "rosidl_generator_cpp/msg/bounded_sequences.hpp" +#include "rosidl_generator_cpp/msg/nested.hpp" #include "rosidl_generator_cpp/msg/strings.hpp" +#include "rosidl_generator_cpp/msg/w_strings.hpp" #include "rosidl_generator_cpp/srv/empty.hpp" using rosidl_generator_traits::is_message; using rosidl_generator_traits::is_service; using rosidl_generator_traits::is_service_request; using rosidl_generator_traits::is_service_response; +using rosidl_generator_traits::to_yaml; + +TEST(Test_rosidl_generator_traits, to_yaml) { + { + const rosidl_generator_cpp::msg::Empty msg; + EXPECT_STREQ("null\n", to_yaml(msg).c_str()); + } + + { + rosidl_generator_cpp::msg::Defaults msg; + msg.float64_value = 1.0; + EXPECT_STREQ( + R"(bool_value: true +byte_value: 0x32 +char_value: 100 +float32_value: 1.12500 +float64_value: 1.00000 +int8_value: -50 +uint8_value: 200 +int16_value: -1000 +uint16_value: 2000 +int32_value: -30000 +uint32_value: 60000 +int64_value: -40000000 +uint64_value: 50000000 +)", + to_yaml( + msg).c_str()); + } + + { + rosidl_generator_cpp::msg::Strings msg; + msg.string_value = "Hello\nworld"; + EXPECT_STREQ( + R"(string_value: "Hello +world" +string_value_default1: "Hello world!" +string_value_default2: "Hello'world!" +string_value_default3: "Hello\"world!" +string_value_default4: "Hello'world!" +string_value_default5: "Hello\"world!" +bounded_string_value: "" +bounded_string_value_default1: "Hello world!" +bounded_string_value_default2: "Hello'world!" +bounded_string_value_default3: "Hello\"world!" +bounded_string_value_default4: "Hello'world!" +bounded_string_value_default5: "Hello\"world!" +)", + to_yaml( + msg).c_str()); + } + + { + rosidl_generator_cpp::msg::WStrings msg; + msg.wstring_value = u"Hello\nwörld"; + EXPECT_STREQ( + R"(wstring_value: "Hello +w\xf6rld" +wstring_value_default1: "Hello world!" +wstring_value_default2: "Hell\xf6 w\xf6rld!" +wstring_value_default3: "\u30cf\u30ed\u30fc\u30ef\u30fc\u30eb\u30c9" +array_of_wstrings: +- "" +- "" +- "" +bounded_sequence_of_wstrings: [] +unbounded_sequence_of_wstrings: [] +)", + to_yaml( + msg).c_str()); + } + + /*{ + test_msgs::idl::IdlOnlyTypes msg; + msg.wchar_value = u'ö'; + msg.long_double_value = 1.125; + EXPECT_STREQ( + R"(wchar_value: "\u00f6" +long_double_value: 1.12500 +)", + to_yaml( + msg).c_str()); + + msg.wchar_value = u'貓'; + EXPECT_STREQ( + R"(wchar_value: "\u8c93" +long_double_value: 1.12500 +)", + to_yaml( + msg).c_str()); + }*/ + + { + rosidl_generator_cpp::msg::Nested msg; + std::string yaml = to_yaml(msg); +#ifdef _WIN32 + // update yaml to handle variance of floating point decimals on Windows + size_t index = 0; + while ((index = yaml.find("0.000000", index)) != std::string::npos) { + yaml = yaml.replace(index, 8, "0.00000"); + } +#endif + EXPECT_STREQ( + R"(basic_types_value: + bool_value: false + byte_value: 0x00 + char_value: 0 + float32_value: 0.00000 + float64_value: 0.00000 + int8_value: 0 + uint8_value: 0 + int16_value: 0 + uint16_value: 0 + int32_value: 0 + uint32_value: 0 + int64_value: 0 + uint64_value: 0 +)", + yaml.c_str()); + } + + { + rosidl_generator_cpp::msg::BoundedSequences msg; + msg.defaults_values.push_back(rosidl_generator_cpp::msg::Defaults()); + std::string yaml = to_yaml(msg); +#ifdef _WIN32 + // update yaml to handle variance of floating point decimals on Windows + size_t index = 0; + while ((index = yaml.find("0.000000", index)) != std::string::npos) { + yaml = yaml.replace(index, 8, "0.00000"); + } +#endif + EXPECT_STREQ( + R"(bool_values: [] +byte_values: [] +char_values: [] +float32_values: [] +float64_values: [] +int8_values: [] +uint8_values: [] +int16_values: [] +uint16_values: [] +int32_values: [] +uint32_values: [] +int64_values: [] +uint64_values: [] +string_values: [] +basic_types_values: [] +constants_values: [] +defaults_values: +- + bool_value: true + byte_value: 0x32 + char_value: 100 + float32_value: 1.12500 + float64_value: 1.12500 + int8_value: -50 + uint8_value: 200 + int16_value: -1000 + uint16_value: 2000 + int32_value: -30000 + uint32_value: 60000 + int64_value: -40000000 + uint64_value: 50000000 +bool_values_default: +- false +- true +- false +byte_values_default: +- 0x00 +- 0x01 +- 0xff +char_values_default: +- 0 +- 1 +- 127 +float32_values_default: +- 1.12500 +- 0.00000 +- -1.12500 +float64_values_default: +- 3.14150 +- 0.00000 +- -3.14150 +int8_values_default: +- 0 +- 127 +- -128 +uint8_values_default: +- 0 +- 1 +- 255 +int16_values_default: +- 0 +- 32767 +- -32768 +uint16_values_default: +- 0 +- 1 +- 65535 +int32_values_default: +- 0 +- 2147483647 +- -2147483648 +uint32_values_default: +- 0 +- 1 +- 4294967295 +int64_values_default: +- 0 +- 9223372036854775807 +- -9223372036854775808 +uint64_values_default: +- 0 +- 1 +- 18446744073709551615 +string_values_default: +- "" +- "max value" +- "min value" +alignment_check: 0 +)", + yaml.c_str()); + } +} // Empty testing struct struct Message {}; diff --git a/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp index df0027497..bc9898165 100644 --- a/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp +++ b/rosidl_runtime_cpp/include/rosidl_runtime_cpp/traits.hpp @@ -15,11 +15,138 @@ #ifndef ROSIDL_RUNTIME_CPP__TRAITS_HPP_ #define ROSIDL_RUNTIME_CPP__TRAITS_HPP_ +#include +#include #include namespace rosidl_generator_traits { +inline void value_to_yaml(bool value, std::ostream & out) +{ + out << (value ? "true" : "false"); +} + +inline void character_value_to_yaml(unsigned char value, std::ostream & out) +{ + auto flags = out.flags(); + out << "0x" << std::hex << std::setw(2) << std::setfill('0') << \ + static_cast(value); + out.flags(flags); +} + +inline void character_value_to_yaml(char16_t value, std::ostream & out) +{ + auto flags = out.flags(); + out << "\"\\u" << std::hex << std::setw(4) << std::setfill('0') << \ + static_cast(value) << "\""; + out.flags(flags); +} + +inline void value_to_yaml(float value, std::ostream & out) +{ + auto flags = out.flags(); + out << std::showpoint << value; + out.flags(flags); +} + +inline void value_to_yaml(double value, std::ostream & out) +{ + auto flags = out.flags(); + out << std::showpoint << value; + out.flags(flags); +} + +inline void value_to_yaml(long double value, std::ostream & out) +{ + auto flags = out.flags(); + out << std::showpoint << value; + out.flags(flags); +} + +inline void value_to_yaml(uint8_t value, std::ostream & out) +{ + out << +value; +} + +inline void value_to_yaml(int8_t value, std::ostream & out) +{ + out << +value; +} + +inline void value_to_yaml(uint16_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(int16_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(uint32_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(int32_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(uint64_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(int64_t value, std::ostream & out) +{ + out << value; +} + +inline void value_to_yaml(const std::string & value, std::ostream & out) +{ + out << "\""; + size_t index = 0; + while (index < value.size()) { + size_t pos = value.find_first_of("\\\"", index); + if (pos == std::string::npos) { + pos = value.size(); + } + out.write(&value[index], pos - index); + if (pos >= value.size()) { + break; + } + out << "\\" << value[pos]; + index = pos + 1; + } + out << "\""; +} + +inline void value_to_yaml(const std::u16string & value, std::ostream & out) +{ + out << "\""; + std::wstring_convert, char16_t> convert; + auto flags = out.flags(); + size_t index = 0; + while (index < value.size()) { + uint_least16_t character = static_cast(value[index]); + if (!(character & 0xff80)) { // ASCII + std::string character_as_string = convert.to_bytes(character); + out << std::hex << character_as_string.c_str(); + } else if (!(character & 0xff00)) { // only 1 byte set + out << "\\x" << std::hex << std::setw(2) << std::setfill('0') << \ + character; + } else { + out << "\\u" << std::hex << std::setw(4) << std::setfill('0') << \ + character; + } + index += 1; + } + out.flags(flags); + out << "\""; +} + template inline const char * data_type();