Skip to content

Commit ff9e9bd

Browse files
bors[bot]Aceeri
andauthored
Merge #596
596: Implement Add/AddAssign/Ord/PartialOrd for GodotString r=toasteater a=Aceeri Heyo, decided to partially work on one of the issues here: #579 Sorry for not commenting on it about working on the issue, but thought it was just small enough to not make too much of a difference. I was also looking at adding the Index/IndexMut traits, but `godot_string_operator_index` and `godot_string_operator_index_const` don't seem to really line up very well with the traits. There is probably a way to do it, just might take a bit more effort. Co-authored-by: Aceeri <conmcclusk@gmail.com>
2 parents 9dd92ea + f11fcc5 commit ff9e9bd

File tree

1 file changed

+172
-2
lines changed

1 file changed

+172
-2
lines changed

gdnative-core/src/core_types/string.rs

Lines changed: 172 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::sys;
33
use crate::NewRef;
44

55
use std::cmp::Ordering;
6+
use std::convert::TryFrom;
67
use std::ffi::CStr;
78
use std::fmt;
89
use std::mem::forget;
9-
use std::ops::Range;
10+
use std::ops::{Add, AddAssign, Index, Range};
1011
use std::slice;
1112
use std::str;
1213

@@ -242,7 +243,7 @@ impl GodotString {
242243
GodotString(sys)
243244
}
244245

245-
/// Clones `sys` into a `GodotString` without droping `sys`
246+
/// Clones `sys` into a `GodotString` without dropping `sys`
246247
#[doc(hidden)]
247248
#[inline]
248249
pub fn clone_from_sys(sys: sys::godot_string) -> Self {
@@ -293,6 +294,151 @@ impl std::hash::Hash for GodotString {
293294
}
294295
}
295296

297+
impl Add<GodotString> for GodotString {
298+
type Output = GodotString;
299+
#[inline]
300+
fn add(self, other: GodotString) -> GodotString {
301+
&self + &other
302+
}
303+
}
304+
305+
impl Add<&GodotString> for &GodotString {
306+
type Output = GodotString;
307+
#[inline]
308+
fn add(self, other: &GodotString) -> GodotString {
309+
GodotString::from_sys(unsafe { (get_api().godot_string_operator_plus)(&self.0, &other.0) })
310+
}
311+
}
312+
313+
impl<S> Add<S> for &GodotString
314+
where
315+
S: AsRef<str>,
316+
{
317+
type Output = GodotString;
318+
#[inline]
319+
fn add(self, other: S) -> GodotString {
320+
self.add(&GodotString::from_str(other))
321+
}
322+
}
323+
324+
/// `AddAssign` implementations copy the strings' contents since `GodotString` is immutable.
325+
impl AddAssign<&GodotString> for GodotString {
326+
#[inline]
327+
fn add_assign(&mut self, other: &Self) {
328+
*self = &*self + other;
329+
}
330+
}
331+
332+
/// `AddAssign` implementations copy the strings' contents since `GodotString` is immutable.
333+
impl AddAssign<GodotString> for GodotString {
334+
#[inline]
335+
fn add_assign(&mut self, other: Self) {
336+
*self += &other;
337+
}
338+
}
339+
340+
/// `AddAssign` implementations copy the strings' contents since `GodotString` is immutable.
341+
impl<S> AddAssign<S> for GodotString
342+
where
343+
S: AsRef<str>,
344+
{
345+
#[inline]
346+
fn add_assign(&mut self, other: S) {
347+
self.add_assign(&GodotString::from_str(other))
348+
}
349+
}
350+
351+
impl PartialOrd for GodotString {
352+
#[inline]
353+
fn partial_cmp(&self, other: &GodotString) -> Option<Ordering> {
354+
Some(self.cmp(other))
355+
}
356+
}
357+
358+
impl Ord for GodotString {
359+
#[inline]
360+
fn cmp(&self, other: &GodotString) -> Ordering {
361+
if self == other {
362+
Ordering::Equal
363+
} else if unsafe { (get_api().godot_string_operator_less)(&self.0, &other.0) } {
364+
Ordering::Less
365+
} else {
366+
Ordering::Greater
367+
}
368+
}
369+
}
370+
371+
/// Type representing a character in Godot's native encoding. Can be converted to and
372+
/// from `char`. Depending on the platform, this might not always be able to represent
373+
/// a full code point.
374+
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
375+
#[repr(transparent)]
376+
pub struct GodotChar(libc::wchar_t);
377+
378+
/// Error indicating that a `GodotChar` cannot be converted to a `char`.
379+
#[derive(Debug)]
380+
pub enum GodotCharError {
381+
/// The character cannot be represented as a Unicode code point.
382+
InvalidCodePoint,
383+
/// The character's encoding cannot be determined on this platform (`wchar_t` is
384+
/// not 8, 16, or 32-bits wide).
385+
UnknownEncoding,
386+
/// The character is part of an incomplete encoding sequence.
387+
IncompleteSequence,
388+
}
389+
390+
impl TryFrom<GodotChar> for char {
391+
type Error = GodotCharError;
392+
393+
#[inline]
394+
fn try_from(c: GodotChar) -> Result<Self, GodotCharError> {
395+
match std::mem::size_of::<libc::wchar_t>() {
396+
1 => std::char::from_u32(c.0 as u32).ok_or(GodotCharError::IncompleteSequence),
397+
4 => std::char::from_u32(c.0 as u32).ok_or(GodotCharError::InvalidCodePoint),
398+
2 => {
399+
let mut iter = std::char::decode_utf16(std::iter::once(c.0 as u16));
400+
let c = iter
401+
.next()
402+
.ok_or(GodotCharError::InvalidCodePoint)?
403+
.map_err(|_| GodotCharError::IncompleteSequence)?;
404+
405+
assert!(
406+
iter.next().is_none(),
407+
"it should be impossible to decode more than one code point from one u16"
408+
);
409+
410+
Ok(c)
411+
}
412+
_ => Err(GodotCharError::UnknownEncoding),
413+
}
414+
}
415+
}
416+
417+
/// Does a best-effort conversion from `GodotChar` to char. If that is not possible,
418+
/// the implementation returns `false`.
419+
impl PartialEq<char> for GodotChar {
420+
#[inline]
421+
fn eq(&self, other: &char) -> bool {
422+
char::try_from(*self).map_or(false, |this| this == *other)
423+
}
424+
}
425+
426+
/// The index operator provides a low-level view of characters in Godot's native encoding
427+
/// that doesn't always correspond to Unicode code points one-to-one. This operation goes
428+
/// through FFI. For intensive string operations, consider converting to a Rust `String`
429+
/// first to avoid this cost.
430+
impl Index<usize> for GodotString {
431+
type Output = GodotChar;
432+
#[inline]
433+
fn index(&self, index: usize) -> &Self::Output {
434+
unsafe {
435+
let c: *const libc::wchar_t =
436+
(get_api().godot_string_operator_index)(self.sys() as *mut _, index as i32);
437+
&*(c as *const GodotChar)
438+
}
439+
}
440+
}
441+
296442
// TODO: Is it useful to expose this type?
297443
// Could just make it an internal detail of how to convert to a rust string.
298444
#[doc(hidden)]
@@ -476,6 +622,30 @@ godot_test!(test_string {
476622
let foo2 = foo.new_ref();
477623
assert!(foo == foo2);
478624

625+
let bar: GodotString = "bar".into();
626+
let qux: GodotString = "qux".into();
627+
assert_eq!(&bar + &qux, "barqux".into());
628+
629+
let baz: GodotString = "baz".into();
630+
assert_eq!(&baz + "corge", "bazcorge".into());
631+
632+
let mut bar2 = bar.new_ref();
633+
bar2 += &qux;
634+
assert_eq!(bar2, "barqux".into());
635+
636+
let cmp1: GodotString = "foo".into();
637+
let cmp2: GodotString = "foo".into();
638+
let cmp3: GodotString = "bar".into();
639+
assert_eq!(cmp1 < cmp2, false, "equal should not be less than");
640+
assert_eq!(cmp1 > cmp2, false, "equal should not be greater than");
641+
assert_eq!(cmp1 < cmp3, false, "foo should be less than bar");
642+
assert_eq!(cmp3 > cmp1, false, "bar should be greater than foo");
643+
644+
let index_string: GodotString = "bar".into();
645+
assert_eq!(index_string[0], 'b');
646+
assert_eq!(index_string[1], 'a');
647+
assert_eq!(index_string[2], 'r');
648+
479649
let variant = Variant::from_godot_string(&foo);
480650
assert!(variant.get_type() == VariantType::GodotString);
481651

0 commit comments

Comments
 (0)