From a4e746376eea707236567b330531df562491034d Mon Sep 17 00:00:00 2001 From: Dirk Thomas Date: Tue, 30 Apr 2019 08:22:29 -0700 Subject: [PATCH] add WString support Signed-off-by: Dirk Thomas --- rosidl_adapter/rosidl_adapter/msg/__init__.py | 12 +++- rosidl_adapter/rosidl_adapter/parser.py | 17 +++-- rosidl_adapter/test/test_base_type.py | 21 +++--- rosidl_adapter/test/test_constant.py | 1 + .../test/test_parse_primitive_value_string.py | 70 +++++++++++++++++++ rosidl_generator_c/CMakeLists.txt | 1 + .../rosidl_generator_c/u16string_functions.h | 10 +++ .../rosidl_generator_c/__init__.py | 7 ++ rosidl_generator_c/src/u16string_functions.c | 30 ++++++++ rosidl_generator_c/test/test_interfaces.c | 36 ++++++++++ rosidl_generator_cpp/CMakeLists.txt | 2 + .../resource/msg__struct.hpp.em | 14 +++- .../rosidl_generator_cpp/__init__.py | 13 +++- rosidl_generator_cpp/test/test_interfaces.cpp | 19 +++++ rosidl_parser/rosidl_parser/grammar.lark | 5 +- rosidl_parser/rosidl_parser/parser.py | 35 +++++++++- rosidl_parser/test/msg/MyMessage.idl | 1 + rosidl_parser/test/test_parser.py | 6 +- .../resource/msg__type_support.cpp.em | 2 +- 19 files changed, 274 insertions(+), 28 deletions(-) diff --git a/rosidl_adapter/rosidl_adapter/msg/__init__.py b/rosidl_adapter/rosidl_adapter/msg/__init__.py index 9428a803c..4f878f356 100644 --- a/rosidl_adapter/rosidl_adapter/msg/__init__.py +++ b/rosidl_adapter/rosidl_adapter/msg/__init__.py @@ -55,6 +55,7 @@ def convert_msg_to_idl(package_dir, package_name, input_file, output_dir): 'float32': 'float', 'float64': 'double', 'string': 'string', + 'wstring': 'wstring', } @@ -69,6 +70,8 @@ def to_idl_literal(idl_type, value): return 'TRUE' if value else 'FALSE' if idl_type.startswith('string'): return string_to_idl_string_literal(value) + if idl_type.startswith('wstring'): + return string_to_idl_wstring_literal(value) return value @@ -79,6 +82,10 @@ def string_to_idl_string_literal(string): return '"{0}"'.format(estr) +def string_to_idl_wstring_literal(string): + return string_to_idl_string_literal(string) + + def get_include_file(base_type): if base_type.is_primitive_type(): return None @@ -90,7 +97,10 @@ def get_idl_type(type_): identifier = MSG_TYPE_TO_IDL[type_] elif type_.is_primitive_type(): identifier = MSG_TYPE_TO_IDL[type_.type] - if identifier == 'string' and type_.string_upper_bound is not None: + if ( + identifier in ('string', 'wstring') and + type_.string_upper_bound is not None + ): identifier += '<{type_.string_upper_bound}>'.format_map(locals()) else: identifier = '{type_.pkg_name}::msg::{type_.type}' \ diff --git a/rosidl_adapter/rosidl_adapter/parser.py b/rosidl_adapter/rosidl_adapter/parser.py index e087e677f..5ca96bc07 100644 --- a/rosidl_adapter/rosidl_adapter/parser.py +++ b/rosidl_adapter/rosidl_adapter/parser.py @@ -51,7 +51,7 @@ 'int64', 'uint64', 'string', - # TODO reconsider wstring / u16string / u32string + 'wstring', # TODO duration and time 'duration', # for compatibility only 'time', # for compatibility only @@ -157,9 +157,12 @@ def __init__(self, type_string, context_package_name=None): self.type = type_string self.string_upper_bound = None - elif type_string.startswith('string%s' % STRING_UPPER_BOUND_TOKEN): + elif ( + type_string.startswith('string%s' % STRING_UPPER_BOUND_TOKEN) or + type_string.startswith('wstring%s' % STRING_UPPER_BOUND_TOKEN) + ): self.pkg_name = None - self.type = 'string' + self.type = type_string.split(STRING_UPPER_BOUND_TOKEN, 1)[0] upper_bound_string = type_string[len(self.type) + len(STRING_UPPER_BOUND_TOKEN):] @@ -321,7 +324,7 @@ def __eq__(self, other): def __str__(self): value = self.value - if self.type == 'string': + if self.type in ('string', 'wstring'): value = "'%s'" % value return '%s %s=%s' % (self.type, self.name, value) @@ -356,7 +359,7 @@ def __str__(self): s = '%s %s' % (str(self.type), self.name) if self.default_value is not None: if self.type.is_primitive_type() and not self.type.is_array and \ - self.type.type == 'string': + self.type.type in ('string', 'wstring'): s += " '%s'" % self.default_value else: s += ' %s' % self.default_value @@ -571,7 +574,7 @@ def parse_value_string(type_, value_string): "array value must start with '[' and end with ']'") elements_string = value_string[1:-1] - if type_.type == 'string': + if type_.type in ('string', 'wstring'): # String arrays need special processing as the comma can be part of a quoted string # and not a separator of array elements value_strings = parse_string_array_value_string(elements_string, type_.array_size) @@ -725,7 +728,7 @@ def parse_primitive_value_string(type_, value_string): return value - if primitive_type == 'string': + if primitive_type in ('string', 'wstring'): # remove outer quotes to allow leading / trailing spaces in the string for quote in ['"', "'"]: if value_string.startswith(quote) and value_string.endswith(quote): diff --git a/rosidl_adapter/test/test_base_type.py b/rosidl_adapter/test/test_base_type.py index 0e79efa92..16eb0d9c5 100644 --- a/rosidl_adapter/test/test_base_type.py +++ b/rosidl_adapter/test/test_base_type.py @@ -33,22 +33,24 @@ def test_base_type_constructor(): 'uint32', 'int64', 'uint64', - 'string'] + 'string', + 'wstring'] for primitive_type in primitive_types: base_type = BaseType(primitive_type) assert base_type.pkg_name is None assert base_type.type == primitive_type assert base_type.string_upper_bound is None - base_type = BaseType('string<=23') - assert base_type.pkg_name is None - assert base_type.type == 'string' - assert base_type.string_upper_bound == 23 + for string_type in ('string', 'wstring'): + base_type = BaseType('%s<=23' % string_type) + assert base_type.pkg_name is None + assert base_type.type == string_type + assert base_type.string_upper_bound == 23 - with pytest.raises(TypeError): - BaseType('string<=upperbound') - with pytest.raises(TypeError): - BaseType('string<=0') + with pytest.raises(TypeError): + BaseType('%s<=upperbound' % string_type) + with pytest.raises(TypeError): + BaseType('%s<=0' % string_type) base_type = BaseType('pkg/Msg') assert base_type.pkg_name == 'pkg' @@ -84,3 +86,4 @@ def test_base_type_methods(): assert str(BaseType('pkg/Foo')) == 'pkg/Foo' assert str(BaseType('bool')) == 'bool' assert str(BaseType('string<=5')) == 'string<=5' + assert str(BaseType('wstring<=5')) == 'wstring<=5' diff --git a/rosidl_adapter/test/test_constant.py b/rosidl_adapter/test/test_constant.py index 6bbfef846..26d396abd 100644 --- a/rosidl_adapter/test/test_constant.py +++ b/rosidl_adapter/test/test_constant.py @@ -42,3 +42,4 @@ def test_constant_methods(): assert str(Constant('bool', 'FOO', '1')) == 'bool FOO=True' assert str(Constant('string', 'FOO', 'foo')) == "string FOO='foo'" + assert str(Constant('wstring', 'FOO', 'foo')) == "wstring FOO='foo'" diff --git a/rosidl_adapter/test/test_parse_primitive_value_string.py b/rosidl_adapter/test/test_parse_primitive_value_string.py index 4e46cca19..4536178b8 100644 --- a/rosidl_adapter/test/test_parse_primitive_value_string.py +++ b/rosidl_adapter/test/test_parse_primitive_value_string.py @@ -172,6 +172,76 @@ def test_parse_primitive_value_string_string(): assert value == '"foo"' +def test_parse_primitive_value_wstring_string(): + value = parse_primitive_value_string( + Type('wstring'), 'foo') + assert value == 'foo' + + value = parse_primitive_value_string( + Type('wstring'), '"foo"') + assert value == 'foo' + + value = parse_primitive_value_string( + Type('wstring'), "'foo'") + assert value == 'foo' + + value = parse_primitive_value_string( + Type('wstring'), '"\'foo\'"') + assert value == "'foo'" + + value = parse_primitive_value_string( + Type('wstring'), '"foo ') + assert value == '"foo ' + + value = parse_primitive_value_string( + Type('wstring<=3'), 'foo') + assert value == 'foo' + + with pytest.raises(InvalidValue): + parse_primitive_value_string( + Type('wstring<=3'), 'foobar') + + with pytest.raises(InvalidValue): + parse_primitive_value_string( + Type('wstring'), r"""'foo''""") + + with pytest.raises(InvalidValue): + parse_primitive_value_string( + Type('wstring'), r'''"foo"bar\"baz"''') # noqa: Q001 + + value = parse_primitive_value_string( + Type('wstring'), '"foo') + assert value == '"foo' + + value = parse_primitive_value_string( + Type('wstring'), '"foo') + assert value == '"foo' + + value = parse_primitive_value_string( + Type('wstring'), "'foo") + assert value == "'foo" + + value = parse_primitive_value_string( + Type('wstring'), '"foo') + assert value == '"foo' + + value = parse_primitive_value_string( + Type('wstring'), "'fo'o") + assert value == "'fo'o" + + value = parse_primitive_value_string( + Type('wstring'), '"fo"o') + assert value == '"fo"o' + + value = parse_primitive_value_string( + Type('wstring'), r'''"'foo'"''') # noqa: Q001 + assert value == "'foo'" + + value = parse_primitive_value_string( + Type('wstring'), r"""'"foo"'""") + assert value == '"foo"' + + def test_parse_primitive_value_string_unknown(): class CustomType(Type): diff --git a/rosidl_generator_c/CMakeLists.txt b/rosidl_generator_c/CMakeLists.txt index 546e2ae4f..f08bde8f1 100644 --- a/rosidl_generator_c/CMakeLists.txt +++ b/rosidl_generator_c/CMakeLists.txt @@ -88,6 +88,7 @@ if(BUILD_TESTING) "msg/Uint8.msg" "msg/Various.msg" "msg/Wire.msg" + "msg/WStrings.msg" "srv/AddTwoInts.srv" ) diff --git a/rosidl_generator_c/include/rosidl_generator_c/u16string_functions.h b/rosidl_generator_c/include/rosidl_generator_c/u16string_functions.h index 83a354374..94c4958be 100644 --- a/rosidl_generator_c/include/rosidl_generator_c/u16string_functions.h +++ b/rosidl_generator_c/include/rosidl_generator_c/u16string_functions.h @@ -45,6 +45,11 @@ bool rosidl_generator_c__U16String__assignn( rosidl_generator_c__U16String * str, const uint16_t * value, size_t n); +ROSIDL_GENERATOR_C_PUBLIC +bool +rosidl_generator_c__U16String__assignn_from_char( + rosidl_generator_c__U16String * str, const char * value, size_t n); + ROSIDL_GENERATOR_C_PUBLIC bool rosidl_generator_c__U16String__assign( @@ -54,6 +59,11 @@ ROSIDL_GENERATOR_C_PUBLIC size_t rosidl_generator_c__U16String__len(const uint16_t * value); +ROSIDL_GENERATOR_C_PUBLIC +bool +rosidl_generator_c__U16String__resize( + rosidl_generator_c__U16String * str, size_t n); + ROSIDL_GENERATOR_C_PUBLIC bool rosidl_generator_c__U16String__Sequence__init( diff --git a/rosidl_generator_c/rosidl_generator_c/__init__.py b/rosidl_generator_c/rosidl_generator_c/__init__.py index fae65e242..fbf565df2 100644 --- a/rosidl_generator_c/rosidl_generator_c/__init__.py +++ b/rosidl_generator_c/rosidl_generator_c/__init__.py @@ -128,6 +128,9 @@ def value_to_c(type_, value): if isinstance(type_, AbstractString): return '"%s"' % escape_string(value) + if isinstance(type_, AbstractWString): + return 'u"%s"' % escape_wstring(value) + return basic_value_to_c(type_, value) @@ -157,3 +160,7 @@ def escape_string(s): s = s.replace('\\', '\\\\') s = s.replace('"', r'\"') return s + + +def escape_wstring(s): + return escape_string(s) diff --git a/rosidl_generator_c/src/u16string_functions.c b/rosidl_generator_c/src/u16string_functions.c index e5683c997..ff58c1b90 100644 --- a/rosidl_generator_c/src/u16string_functions.c +++ b/rosidl_generator_c/src/u16string_functions.c @@ -94,6 +94,18 @@ rosidl_generator_c__U16String__assignn( return true; } +bool +rosidl_generator_c__U16String__assignn_from_char( + rosidl_generator_c__U16String * str, const char * value, size_t n) +{ + // since n represents the number of 8-bit characters it must be an even number + if (n % 2 != 0) { + return false; + } + return rosidl_generator_c__U16String__assignn( + str, (const uint16_t *)value, n / 2); +} + bool rosidl_generator_c__U16String__assign( rosidl_generator_c__U16String * str, const uint16_t * value) @@ -115,6 +127,24 @@ rosidl_generator_c__U16String__len(const uint16_t * value) return len; } +bool +rosidl_generator_c__U16String__resize( + rosidl_generator_c__U16String * str, size_t n) +{ + if (!str) { + return false; + } + uint16_t * data = realloc(str->data, (n + 1) * sizeof(uint16_t)); + if (!data) { + return false; + } + data[n] = 0; + str->data = data; + str->size = n; + str->capacity = n + 1; + return true; +} + bool rosidl_generator_c__U16String__Sequence__init( rosidl_generator_c__U16String__Sequence * sequence, size_t size) diff --git a/rosidl_generator_c/test/test_interfaces.c b/rosidl_generator_c/test/test_interfaces.c index 6fc230315..538bef261 100644 --- a/rosidl_generator_c/test/test_interfaces.c +++ b/rosidl_generator_c/test/test_interfaces.c @@ -23,6 +23,7 @@ #include "rosidl_generator_c/primitives_sequence_functions.h" #include "rosidl_generator_c/string_functions.h" +#include "rosidl_generator_c/u16string_functions.h" #include "rosidl_generator_c/msg/bool.h" #include "rosidl_generator_c/msg/bounded_array_nested.h" @@ -56,6 +57,7 @@ #include "rosidl_generator_c/msg/uint8.h" #include "rosidl_generator_c/msg/various.h" #include "rosidl_generator_c/msg/wire.h" +#include "rosidl_generator_c/msg/w_strings.h" #define TEST_STRING \ "Deep into that darkness peering, long I stood there wondering, fearing" @@ -63,6 +65,8 @@ "The quick brown fox jumps over the lazy dog." #define TEST_STRING3 \ "PACK MY BOX WITH FIVE DOZEN LIQUOR JUGS." +#define TEST_WSTRING \ + u"Deep into that darkness peering, long I stood there wondering, fearing \u2122" #define ARRAY_SIZE 7 #define STRINGIFY(x) _STRINGIFY(x) @@ -317,6 +321,7 @@ int test_strings(void) EXPECT_EQ(0, strcmp(strings->empty_string.data, TEST_STRING)); EXPECT_EQ(0, strcmp(strings->def_string.data, "Hello world!")); EXPECT_EQ(0, strcmp(strings->def_string2.data, "Hello'world!")); + fprintf(stderr, "*%s*\n", strings->def_string3.data); EXPECT_EQ(0, strcmp(strings->def_string3.data, "Hello\"world!")); EXPECT_EQ(0, strcmp(strings->def_string4.data, "Hello'world!")); EXPECT_EQ(0, strcmp(strings->def_string5.data, "Hello\"world!")); @@ -333,6 +338,37 @@ int test_strings(void) return 0; } +/** + * Test message with different wstring types. + */ +int test_wstrings(void) +{ + bool res = false; + rosidl_generator_c__msg__WStrings * wstrings = NULL; + + wstrings = rosidl_generator_c__msg__WStrings__create(); + EXPECT_NE(wstrings, NULL); + + res = rosidl_generator_c__U16String__assign(&wstrings->empty_wstring, TEST_WSTRING); + EXPECT_EQ(true, res); + // EXPECT_EQ(0, strcmp(wstrings->empty_wstring.data, TEST_WSTRING)); + // EXPECT_EQ(0, strcmp(wstrings->def_wstring.data, "Hello world!")); + // EXPECT_EQ(0, strcmp(wstrings->def_wstring2.data, "Hello'world!")); + // EXPECT_EQ(0, strcmp(wstrings->def_wstring3.data, "Hello\"world!")); + // EXPECT_EQ(0, strcmp(wstrings->def_wstring4.data, "Hello'world!")); + // EXPECT_EQ(0, strcmp(wstrings->def_wstring5.data, "Hello\"world!")); + // since upper-bound checking is not implemented yet, we restrict the string copying + // TODO(mikaelarguedas) Test string length properly instead of cheating copy + // res = rosidl_generator_c__String__assign(&wstrings->ub_wstring, TEST_WSTRING); + res = rosidl_generator_c__U16String__assignn(&wstrings->ub_wstring, TEST_WSTRING, 24); + EXPECT_EQ(true, res); + // EXPECT_EQ(0, strcmp(wstrings->ub_wstring.data, "Deep into that darknes")); + // EXPECT_EQ(0, strcmp(wstrings->ub_def_wstring.data, "Upper bounded string.")); + + rosidl_generator_c__msg__WStrings__destroy(wstrings); + + return 0; +} /** * Test message with different string array types. diff --git a/rosidl_generator_cpp/CMakeLists.txt b/rosidl_generator_cpp/CMakeLists.txt index 1e390fd56..4ee5b2316 100644 --- a/rosidl_generator_cpp/CMakeLists.txt +++ b/rosidl_generator_cpp/CMakeLists.txt @@ -50,6 +50,8 @@ if(BUILD_TESTING) "msg/UnboundedArrayUnbounded.msg" "msg/Various.msg" + + "msg/WString.msg" ) set(srv_files diff --git a/rosidl_generator_cpp/resource/msg__struct.hpp.em b/rosidl_generator_cpp/resource/msg__struct.hpp.em index 001bb76e9..2a880f45b 100644 --- a/rosidl_generator_cpp/resource/msg__struct.hpp.em +++ b/rosidl_generator_cpp/resource/msg__struct.hpp.em @@ -13,10 +13,12 @@ @{ from rosidl_generator_cpp import create_init_alloc_and_member_lists from rosidl_generator_cpp import escape_string +from rosidl_generator_cpp import escape_wstring from rosidl_generator_cpp import msg_type_to_cpp from rosidl_generator_cpp import MSG_TYPE_TO_CPP -from rosidl_parser.definition import AbstractGenericString from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import AbstractString +from rosidl_parser.definition import AbstractWString from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX from rosidl_parser.definition import ACTION_GOAL_SUFFIX from rosidl_parser.definition import ACTION_RESULT_SUFFIX @@ -258,8 +260,10 @@ non_defaulted_zero_initialized_members = [ // constant declarations @[for constant in message.constants]@ -@[ if isinstance(constant.type, AbstractGenericString)]@ +@[ if isinstance(constant.type, AbstractString)]@ static const @(MSG_TYPE_TO_CPP['string']) @(constant.name); +@[ elif isinstance(constant.type, AbstractWString)]@ + static const @(MSG_TYPE_TO_CPP['wstring']) @(constant.name); @[ else]@ static constexpr @(MSG_TYPE_TO_CPP[constant.type.typename]) @(constant.name) = @[ if isinstance(constant.type, BasicType) and constant.type.typename in (*INTEGER_TYPES, *CHARACTER_TYPES, BOOLEAN_TYPE, OCTET_TYPE)]@ @@ -335,10 +339,14 @@ using @(message.structure.namespaced_type.name) = // constant definitions @[for c in message.constants]@ -@[ if isinstance(c.type, AbstractGenericString)]@ +@[ if isinstance(c.type, AbstractString)]@ template const @(MSG_TYPE_TO_CPP['string']) @(message.structure.namespaced_type.name)_::@(c.name) = "@(escape_string(c.value))"; +@[ elif isinstance(c.type, AbstractWString)]@ +template +const @(MSG_TYPE_TO_CPP['wstring']) +@(message.structure.namespaced_type.name)_::@(c.name) = u"@(escape_wstring(c.value))"; @[ else ]@ template constexpr @(MSG_TYPE_TO_CPP[c.type.typename]) @(message.structure.namespaced_type.name)_::@(c.name); diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index af8f5bd4f..84adf2113 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -55,6 +55,8 @@ def generate_cpp(generator_arguments_file): 'int64': 'int64_t', 'string': 'std::basic_string, ' + 'typename ContainerAllocator::template rebind::other>', + 'wstring': 'std::basic_string, ' + + 'typename ContainerAllocator::template rebind::other>', } @@ -75,7 +77,7 @@ def msg_type_only_to_cpp(type_): elif isinstance(type_, AbstractString): cpp_type = MSG_TYPE_TO_CPP['string'] elif isinstance(type_, AbstractWString): - assert False, 'TBD' + cpp_type = MSG_TYPE_TO_CPP['wstring'] elif isinstance(type_, NamespacedType): typename = '::'.join(type_.namespaced_name()) cpp_type = typename + '_' @@ -167,9 +169,12 @@ def primitive_value_to_cpp(type_, value): "Could not convert non-primitive type '%s' to CPP" % (type_) assert value is not None, "Value for type '%s' must not be None" % (type_) - if isinstance(type_, AbstractGenericString): + if isinstance(type_, AbstractString): return '"%s"' % escape_string(value) + if isinstance(type_, AbstractWString): + return 'u"%s"' % escape_wstring(value) + if type_.typename == 'boolean': return 'true' if value else 'false' @@ -217,6 +222,10 @@ def escape_string(s): return s +def escape_wstring(s): + return escape_string(s) + + def create_init_alloc_and_member_lists(message): # A Member object represents the information we need to know to initialize # a single member of the class. diff --git a/rosidl_generator_cpp/test/test_interfaces.cpp b/rosidl_generator_cpp/test/test_interfaces.cpp index 9588bf731..1a8819821 100644 --- a/rosidl_generator_cpp/test/test_interfaces.cpp +++ b/rosidl_generator_cpp/test/test_interfaces.cpp @@ -49,6 +49,8 @@ #include "rosidl_generator_cpp/msg/unbounded_array_static.hpp" #include "rosidl_generator_cpp/msg/unbounded_array_unbounded.hpp" +#include "rosidl_generator_cpp/msg/w_string.hpp" + #define PRIMITIVES_ARRAY_SIZE 10 #define BOUNDED_STRING_LENGTH 10 #define SUBMESSAGE_ARRAY_SIZE 3 @@ -136,6 +138,10 @@ TEST(Test_rosidl_generator_traits, has_fixed_size) { !rosidl_generator_traits::has_fixed_size< rosidl_generator_cpp::msg::UnboundedArrayUnbounded>::value, "UnboundedArrayUnbounded::has_fixed_size is true"); + + static_assert( + !rosidl_generator_traits::has_fixed_size::value, + "WString::has_fixed_size is true"); } TEST(Test_rosidl_generator_traits, has_bounded_size) { @@ -224,6 +230,10 @@ TEST(Test_rosidl_generator_traits, has_bounded_size) { !rosidl_generator_traits::has_bounded_size< rosidl_generator_cpp::msg::UnboundedArrayUnbounded>::value, "UnboundedArrayUnbounded::has_bounded_size is true"); + + static_assert( + !rosidl_generator_traits::has_bounded_size::value, + "WString::has_bounded_size is true"); } #define TEST_PRIMITIVE_FIELD_ASSIGNMENT(Message, FieldName, InitialValue, FinalValue) \ @@ -238,6 +248,10 @@ TEST(Test_rosidl_generator_traits, has_bounded_size) { Message.FieldName = FinalValue; \ ASSERT_STREQ(FinalValue, Message.FieldName.c_str()); +#define TEST_WSTRING_FIELD_ASSIGNMENT(Message, FieldName, InitialValue, FinalValue) \ + Message.FieldName = InitialValue; \ + Message.FieldName = FinalValue; + void test_message_primitives_static(rosidl_generator_cpp::msg::PrimitivesStatic message) { // workaround for https://github.com/google/googletest/issues/322 @@ -601,6 +615,11 @@ TEST(Test_messages, Test_string) { TEST_STRING_FIELD_ASSIGNMENT(message, string_value, "", "Deep into") } +TEST(Test_messages, Test_wstring) { + rosidl_generator_cpp::msg::WString message; + TEST_WSTRING_FIELD_ASSIGNMENT(message, wstring_value, u"", u"wstring_value_\u2122") +} + #define TEST_STATIC_ARRAY_STRING( \ Message, FieldName, PrimitiveType, ArraySize, MinVal, MaxVal, MinLength, MaxLength) \ std::array pattern_ ## FieldName; \ diff --git a/rosidl_parser/rosidl_parser/grammar.lark b/rosidl_parser/rosidl_parser/grammar.lark index 7872162bd..4f78af493 100644 --- a/rosidl_parser/rosidl_parser/grammar.lark +++ b/rosidl_parser/rosidl_parser/grammar.lark @@ -66,9 +66,10 @@ _ESCAPE_SEQUENCES: "\\n" | "\\t" | "\\v" | "\\b" | "\\r" | "\\f" | "\\a" | "\\\\ // 7.2.6.3 String Literals // string_literal: "\"" CHAR* "\"" -// replace precise rule based on the spec with regex for parsing performance +// wide_string_literal: "L\"" CHAR* "\"" +// replace precise rules based on the spec with regex for parsing performance string_literal: "\"\"" | "\"" /(\\\"|[^"])+/ "\"" -wide_string_literal: "L\"" CHAR* "\"" +wide_string_literal: "L\"\"" | "L\"" /(\\\"|[^"])+/ "\"" // 7.2.6.4 Floating-point Literals floating_pt_literal: DIGIT+ floating_pt_literal_dot DIGIT+ floating_pt_literal_e DIGIT* diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 2eba9787c..69f14664d 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -12,7 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import codecs import os +import re import sys from lark import Lark @@ -556,7 +558,11 @@ def get_const_expr_value(const_expr): if child.data == 'string_literal': assert not negate_value - return get_string_literal_value(child) + return get_string_literal_value(child, allow_unicode=False) + + if child.data == 'wide_string_literal': + assert not negate_value + return get_string_literal_value(child, allow_unicode=True) assert False, 'Unsupported tree: ' + str(const_expr) @@ -582,11 +588,14 @@ def get_floating_pt_literal_value(floating_pt_literal): return float(value) -def get_string_literal_value(string_literal): +def get_string_literal_value(string_literal, *, allow_unicode=False): assert len(string_literal.children) == 1 child = string_literal.children[0] assert isinstance(child, Token) value = child.value + + regex = _get_escape_sequences_regex(allow_unicode=allow_unicode) + value = regex.sub(_decode_escape_sequence, value) # unescape double quote and backslash if preceeded by a backslash i = 0 while i < len(value): @@ -595,3 +604,25 @@ def get_string_literal_value(string_literal): value = value[:i] + value[i + 1:] i += 1 return value + + +def _get_escape_sequences_regex(*, allow_unicode): + # IDL Table 7-9: Escape sequences + pattern = '(' + # newline, horizontal tab, vertical tab, backspace, carriage return, + # form feed, alert, backslash, question mark, single quote, double quote + pattern += '\\[ntvbrfa\\?\'"]' + # octal number + pattern += '|' + '\\[0-7]{1,3}' + # hexadecimal number + pattern += '|' + r'\\x.{1,2}' + if allow_unicode: + # unicode character + pattern += '|' + '\\u.{1,4}' + pattern += ')' + + return re.compile(pattern) + + +def _decode_escape_sequence(match): + return codecs.decode(match.group(0), 'unicode-escape') diff --git a/rosidl_parser/test/msg/MyMessage.idl b/rosidl_parser/test/msg/MyMessage.idl index 0dbac44d4..9105e4f3c 100644 --- a/rosidl_parser/test/msg/MyMessage.idl +++ b/rosidl_parser/test/msg/MyMessage.idl @@ -9,6 +9,7 @@ module rosidl_parser { const float FLOAT_CONSTANT = 1.25; const boolean BOOLEAN_CONSTANT = TRUE; const string STRING_CONSTANT = "string_value"; + const wstring WSTRING_CONSTANT = "wstring_value_\u2122"; }; @verbatim ( language="comment", text="Documentation of MyMessage." ) diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index 3ae4f5825..b479f7276 100644 --- a/rosidl_parser/test/test_parser.py +++ b/rosidl_parser/test/test_parser.py @@ -62,7 +62,7 @@ def test_message_parser_structure(message_idl_file): assert len(messages) == 1 constants = messages[0].constants - assert len(constants) == 5 + assert len(constants) == 6 assert constants[0].name == 'SHORT_CONSTANT' assert isinstance(constants[0].type, BasicType) @@ -88,6 +88,10 @@ def test_message_parser_structure(message_idl_file): assert isinstance(constants[4].type, BoundedString) assert constants[4].value == 'string_value' + assert constants[5].name == 'WSTRING_CONSTANT' + assert isinstance(constants[5].type, BoundedWString) + assert constants[5].value == 'wstring_value_\\u2122' + structure = messages[0].structure assert structure.namespaced_type.namespaces == ['rosidl_parser', 'msg'] assert structure.namespaced_type.name == 'MyMessage' diff --git a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em index eaee15859..edc250421 100644 --- a/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em +++ b/rosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em @@ -65,7 +65,7 @@ if isinstance(member.type.value_type, BasicType): elif isinstance(member.type.value_type, AbstractString): type_ = 'std::string' elif isinstance(member.type.value_type, AbstractWString): - assert False, 'Unknown type: ' + str(member.type.value_type) + type_ = 'std::u16string' elif isinstance(member.type.value_type, NamespacedType): type_ = '::'.join(member.type.value_type.namespaced_name()) }@