Skip to content
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 120 additions & 4 deletions rosidl_runtime_rs/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ffi::CStr;
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::ptr;

#[cfg(feature = "serde")]
mod serde;
Expand Down Expand Up @@ -112,7 +113,7 @@ pub struct StringExceedsBoundsError {

// There is a lot of redundancy between String and WString, which this macro aims to reduce.
macro_rules! string_impl {
($string:ty, $char_type:ty, $unsigned_char_type:ty, $string_conversion_func:ident, $init:ident, $fini:ident, $assignn:ident, $sequence_init:ident, $sequence_fini:ident, $sequence_copy:ident) => {
($string:ty, $char_type:ty, $unsigned_char_type:ty, $string_conversion_func:ident, $encoding_func:ident, $init:ident, $fini:ident, $assignn:ident, $sequence_init:ident, $sequence_fini:ident, $sequence_copy:ident) => {
#[link(name = "rosidl_runtime_c")]
extern "C" {
fn $init(s: *mut $string) -> bool;
Expand All @@ -135,7 +136,7 @@ macro_rules! string_impl {
};
// SAFETY: Passing in a zeroed string is safe.
if !unsafe { $init(&mut msg as *mut _) } {
panic!("Sinit failed");
panic!("$init failed");
}
msg
}
Expand Down Expand Up @@ -224,6 +225,59 @@ macro_rules! string_impl {
}
}

impl FromIterator<char> for $string {
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let mut buf = <$string>::default();
buf.extend(iter);
buf
}
}

impl<'a> FromIterator<&'a char> for $string {
fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> Self {
let mut buf = <$string>::default();
buf.extend(iter);
buf
}
}

impl Extend<char> for $string {
fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
let mut v = self.to_vec();
let iterator = iter.into_iter();

iterator.for_each(|c: char| {
// UTF-8 and UTF-16 encoding requires at least 4 bytes for any character.
// Technically UTF-16 could just use a buffer size of 2 here, but that's more
// trouble than its worth since we are defining this function in the macro.
// See https://doc.rust-lang.org/std/primitive.char.html#method.encode_utf8
let mut buf = [0; 4];
c.$encoding_func(&mut buf);

let filtered_bytes: Vec<$unsigned_char_type> = buf
.into_iter()
.filter(|&c| c != (0 as $unsigned_char_type))
.collect();

for encoded_char in filtered_bytes {
v.push(encoded_char as $char_type);
}
});

// SAFETY: It's okay to pass a non-zero-terminated string here since assignn
// uses the specified length and will append the 0 to the dest string itself.
if !unsafe { $assignn(self as *mut _, v.as_ptr() as *const _, v.len()) } {
panic!("$assignn failed");
}
}
}

impl<'a> Extend<&'a char> for $string {
fn extend<I: IntoIterator<Item = &'a char>>(&mut self, iter: I) {
self.extend(iter.into_iter().cloned());
}
}

// SAFETY: A string is a simple data structure, and therefore not thread-specific.
unsafe impl Send for $string {}
// SAFETY: A string does not have interior mutability, so it can be shared.
Expand Down Expand Up @@ -251,6 +305,7 @@ string_impl!(
std::os::raw::c_char,
u8,
from_utf8_lossy,
encode_utf8,
rosidl_runtime_c__String__init,
rosidl_runtime_c__String__fini,
rosidl_runtime_c__String__assignn,
Expand All @@ -263,6 +318,7 @@ string_impl!(
std::os::raw::c_ushort,
u16,
from_utf16_lossy,
encode_utf16,
rosidl_runtime_c__U16String__init,
rosidl_runtime_c__U16String__fini,
rosidl_runtime_c__U16String__assignn,
Expand All @@ -274,7 +330,7 @@ string_impl!(
impl From<&str> for String {
fn from(s: &str) -> Self {
let mut msg = Self {
data: std::ptr::null_mut(),
data: ptr::null_mut(),
size: 0,
capacity: 0,
};
Expand Down Expand Up @@ -304,7 +360,7 @@ impl String {
impl From<&str> for WString {
fn from(s: &str) -> Self {
let mut msg = Self {
data: std::ptr::null_mut(),
data: ptr::null_mut(),
size: 0,
capacity: 0,
};
Expand Down Expand Up @@ -503,4 +559,64 @@ mod tests {
s.as_str().try_into().unwrap()
}
}

#[test]
fn string_from_char_iterator() {
// Base char case
let expected = String::from("abc");
let actual = "abc".chars().collect::<String>();

assert_eq!(expected, actual);

// Empty case
let expected = String::from("");
let actual = "".chars().collect::<String>();

assert_eq!(expected, actual);

// Non-ascii char case
let expected = String::from("Grüß Gott! 𝕊");
let actual = "Grüß Gott! 𝕊".chars().collect::<String>();

assert_eq!(expected, actual);
}

#[test]
fn extend_string_with_char_iterator() {
let expected = WString::from("abcdef");
let mut actual = WString::from("abc");
actual.extend("def".chars());

assert_eq!(expected, actual);
}

#[test]
fn wstring_from_char_iterator() {
// Base char case
let expected = WString::from("abc");
let actual = "abc".chars().collect::<WString>();

assert_eq!(expected, actual);

// Empty case
let expected = WString::from("");
let actual = "".chars().collect::<WString>();

assert_eq!(expected, actual);

// Non-ascii char case
let expected = WString::from("Grüß Gott! 𝕊");
let actual = "Grüß Gott! 𝕊".chars().collect::<WString>();

assert_eq!(expected, actual);
}

#[test]
fn extend_wstring_with_char_iterator() {
let expected = WString::from("abcdef");
let mut actual = WString::from("abc");
actual.extend("def".chars());

assert_eq!(expected, actual);
}
}