mtl_foundation/string.rs
1//! String type for Foundation.
2//!
3//! Corresponds to `Foundation/NSString.hpp`.
4//!
5//! # C++ Equivalent
6//!
7//! ```cpp
8//! namespace NS {
9//! _NS_ENUM(NS::UInteger, StringEncoding) {
10//! ASCIIStringEncoding = 1,
11//! NEXTSTEPStringEncoding = 2,
12//! JapaneseEUCStringEncoding = 3,
13//! UTF8StringEncoding = 4,
14//! ISOLatin1StringEncoding = 5,
15//! SymbolStringEncoding = 6,
16//! NonLossyASCIIStringEncoding = 7,
17//! ShiftJISStringEncoding = 8,
18//! ISOLatin2StringEncoding = 9,
19//! UnicodeStringEncoding = 10,
20//! WindowsCP1251StringEncoding = 11,
21//! WindowsCP1252StringEncoding = 12,
22//! WindowsCP1253StringEncoding = 13,
23//! WindowsCP1254StringEncoding = 14,
24//! WindowsCP1250StringEncoding = 15,
25//! ISO2022JPStringEncoding = 21,
26//! MacOSRomanStringEncoding = 30,
27//! UTF16StringEncoding = UnicodeStringEncoding,
28//! UTF16BigEndianStringEncoding = 0x90000100,
29//! UTF16LittleEndianStringEncoding = 0x94000100,
30//! UTF32StringEncoding = 0x8c000100,
31//! UTF32BigEndianStringEncoding = 0x98000100,
32//! UTF32LittleEndianStringEncoding = 0x9c000100
33//! };
34//!
35//! _NS_OPTIONS(NS::UInteger, StringCompareOptions) {
36//! CaseInsensitiveSearch = 1,
37//! LiteralSearch = 2,
38//! BackwardsSearch = 4,
39//! AnchoredSearch = 8,
40//! NumericSearch = 64,
41//! DiacriticInsensitiveSearch = 128,
42//! WidthInsensitiveSearch = 256,
43//! ForcedOrderingSearch = 512,
44//! RegularExpressionSearch = 1024
45//! };
46//!
47//! using unichar = unsigned short;
48//!
49//! class String : public Copying<String> {
50//! public:
51//! static String* string();
52//! static String* string(const String* pString);
53//! static String* string(const char* pString, StringEncoding encoding);
54//! static String* alloc();
55//! String* init();
56//! String* init(const String* pString);
57//! String* init(const char* pString, StringEncoding encoding);
58//! String* init(void* pBytes, UInteger len, StringEncoding encoding, bool freeBuffer);
59//! unichar character(UInteger index) const;
60//! UInteger length() const;
61//! const char* cString(StringEncoding encoding) const;
62//! const char* utf8String() const;
63//! UInteger maximumLengthOfBytes(StringEncoding encoding) const;
64//! UInteger lengthOfBytes(StringEncoding encoding) const;
65//! bool isEqualToString(const String* pString) const;
66//! Range rangeOfString(const String* pString, StringCompareOptions options) const;
67//! const char* fileSystemRepresentation() const;
68//! String* stringByAppendingString(const String* pString) const;
69//! ComparisonResult caseInsensitiveCompare(const String* pString) const;
70//! };
71//! }
72//! ```
73
74use std::ffi::{c_char, c_void};
75use std::ptr::NonNull;
76
77use mtl_sys::{class, msg_send_0, msg_send_1, msg_send_2, msg_send_4, sel};
78
79use crate::objc_runtime::ComparisonResult;
80use crate::object::{Copying, Referencing};
81use crate::range::Range;
82use crate::types::UInteger;
83
84/// Unicode character type.
85///
86/// C++ equivalent: `NS::unichar`
87pub type Unichar = u16;
88
89/// String encoding options.
90///
91/// C++ equivalent: `NS::StringEncoding`
92#[repr(transparent)]
93#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
94pub struct StringEncoding(pub UInteger);
95
96impl StringEncoding {
97 /// ASCII encoding.
98 ///
99 /// C++ equivalent: `NS::ASCIIStringEncoding`
100 pub const ASCII: Self = Self(1);
101
102 /// NEXTSTEP encoding.
103 ///
104 /// C++ equivalent: `NS::NEXTSTEPStringEncoding`
105 pub const NEXTSTEP: Self = Self(2);
106
107 /// Japanese EUC encoding.
108 ///
109 /// C++ equivalent: `NS::JapaneseEUCStringEncoding`
110 pub const JAPANESE_EUC: Self = Self(3);
111
112 /// UTF-8 encoding.
113 ///
114 /// C++ equivalent: `NS::UTF8StringEncoding`
115 pub const UTF8: Self = Self(4);
116
117 /// ISO Latin 1 encoding.
118 ///
119 /// C++ equivalent: `NS::ISOLatin1StringEncoding`
120 pub const ISO_LATIN1: Self = Self(5);
121
122 /// Symbol encoding.
123 ///
124 /// C++ equivalent: `NS::SymbolStringEncoding`
125 pub const SYMBOL: Self = Self(6);
126
127 /// Non-lossy ASCII encoding.
128 ///
129 /// C++ equivalent: `NS::NonLossyASCIIStringEncoding`
130 pub const NON_LOSSY_ASCII: Self = Self(7);
131
132 /// Shift JIS encoding.
133 ///
134 /// C++ equivalent: `NS::ShiftJISStringEncoding`
135 pub const SHIFT_JIS: Self = Self(8);
136
137 /// ISO Latin 2 encoding.
138 ///
139 /// C++ equivalent: `NS::ISOLatin2StringEncoding`
140 pub const ISO_LATIN2: Self = Self(9);
141
142 /// Unicode encoding.
143 ///
144 /// C++ equivalent: `NS::UnicodeStringEncoding`
145 pub const UNICODE: Self = Self(10);
146
147 /// Windows CP1251 encoding.
148 ///
149 /// C++ equivalent: `NS::WindowsCP1251StringEncoding`
150 pub const WINDOWS_CP1251: Self = Self(11);
151
152 /// Windows CP1252 encoding.
153 ///
154 /// C++ equivalent: `NS::WindowsCP1252StringEncoding`
155 pub const WINDOWS_CP1252: Self = Self(12);
156
157 /// Windows CP1253 encoding.
158 ///
159 /// C++ equivalent: `NS::WindowsCP1253StringEncoding`
160 pub const WINDOWS_CP1253: Self = Self(13);
161
162 /// Windows CP1254 encoding.
163 ///
164 /// C++ equivalent: `NS::WindowsCP1254StringEncoding`
165 pub const WINDOWS_CP1254: Self = Self(14);
166
167 /// Windows CP1250 encoding.
168 ///
169 /// C++ equivalent: `NS::WindowsCP1250StringEncoding`
170 pub const WINDOWS_CP1250: Self = Self(15);
171
172 /// ISO 2022 JP encoding.
173 ///
174 /// C++ equivalent: `NS::ISO2022JPStringEncoding`
175 pub const ISO2022JP: Self = Self(21);
176
177 /// Mac OS Roman encoding.
178 ///
179 /// C++ equivalent: `NS::MacOSRomanStringEncoding`
180 pub const MAC_OS_ROMAN: Self = Self(30);
181
182 /// UTF-16 encoding (alias for Unicode).
183 ///
184 /// C++ equivalent: `NS::UTF16StringEncoding`
185 pub const UTF16: Self = Self(10);
186
187 /// UTF-16 big endian encoding.
188 ///
189 /// C++ equivalent: `NS::UTF16BigEndianStringEncoding`
190 pub const UTF16_BIG_ENDIAN: Self = Self(0x90000100);
191
192 /// UTF-16 little endian encoding.
193 ///
194 /// C++ equivalent: `NS::UTF16LittleEndianStringEncoding`
195 pub const UTF16_LITTLE_ENDIAN: Self = Self(0x94000100);
196
197 /// UTF-32 encoding.
198 ///
199 /// C++ equivalent: `NS::UTF32StringEncoding`
200 pub const UTF32: Self = Self(0x8c000100);
201
202 /// UTF-32 big endian encoding.
203 ///
204 /// C++ equivalent: `NS::UTF32BigEndianStringEncoding`
205 pub const UTF32_BIG_ENDIAN: Self = Self(0x98000100);
206
207 /// UTF-32 little endian encoding.
208 ///
209 /// C++ equivalent: `NS::UTF32LittleEndianStringEncoding`
210 pub const UTF32_LITTLE_ENDIAN: Self = Self(0x9c000100);
211
212 /// Returns the raw value.
213 #[inline]
214 pub const fn raw(&self) -> UInteger {
215 self.0
216 }
217
218 /// Creates from a raw value.
219 #[inline]
220 pub const fn from_raw(value: UInteger) -> Self {
221 Self(value)
222 }
223}
224
225/// String comparison options.
226///
227/// C++ equivalent: `NS::StringCompareOptions`
228#[repr(transparent)]
229#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
230pub struct StringCompareOptions(pub UInteger);
231
232impl StringCompareOptions {
233 /// No options.
234 pub const NONE: Self = Self(0);
235
236 /// Case insensitive search.
237 ///
238 /// C++ equivalent: `NS::CaseInsensitiveSearch`
239 pub const CASE_INSENSITIVE: Self = Self(1);
240
241 /// Literal search.
242 ///
243 /// C++ equivalent: `NS::LiteralSearch`
244 pub const LITERAL: Self = Self(2);
245
246 /// Backwards search.
247 ///
248 /// C++ equivalent: `NS::BackwardsSearch`
249 pub const BACKWARDS: Self = Self(4);
250
251 /// Anchored search.
252 ///
253 /// C++ equivalent: `NS::AnchoredSearch`
254 pub const ANCHORED: Self = Self(8);
255
256 /// Numeric search.
257 ///
258 /// C++ equivalent: `NS::NumericSearch`
259 pub const NUMERIC: Self = Self(64);
260
261 /// Diacritic insensitive search.
262 ///
263 /// C++ equivalent: `NS::DiacriticInsensitiveSearch`
264 pub const DIACRITIC_INSENSITIVE: Self = Self(128);
265
266 /// Width insensitive search.
267 ///
268 /// C++ equivalent: `NS::WidthInsensitiveSearch`
269 pub const WIDTH_INSENSITIVE: Self = Self(256);
270
271 /// Forced ordering search.
272 ///
273 /// C++ equivalent: `NS::ForcedOrderingSearch`
274 pub const FORCED_ORDERING: Self = Self(512);
275
276 /// Regular expression search.
277 ///
278 /// C++ equivalent: `NS::RegularExpressionSearch`
279 pub const REGULAR_EXPRESSION: Self = Self(1024);
280
281 /// Returns the raw bits.
282 #[inline]
283 pub const fn bits(&self) -> UInteger {
284 self.0
285 }
286
287 /// Creates from raw bits.
288 #[inline]
289 pub const fn from_bits(bits: UInteger) -> Self {
290 Self(bits)
291 }
292
293 /// Check if empty.
294 #[inline]
295 pub const fn is_empty(&self) -> bool {
296 self.0 == 0
297 }
298
299 /// Check if contains all flags in other.
300 #[inline]
301 pub const fn contains(&self, other: Self) -> bool {
302 (self.0 & other.0) == other.0
303 }
304
305 /// Insert flags.
306 #[inline]
307 pub fn insert(&mut self, other: Self) {
308 self.0 |= other.0;
309 }
310
311 /// Remove flags.
312 #[inline]
313 pub fn remove(&mut self, other: Self) {
314 self.0 &= !other.0;
315 }
316}
317
318impl std::ops::BitOr for StringCompareOptions {
319 type Output = Self;
320 #[inline]
321 fn bitor(self, rhs: Self) -> Self {
322 Self(self.0 | rhs.0)
323 }
324}
325
326impl std::ops::BitAnd for StringCompareOptions {
327 type Output = Self;
328 #[inline]
329 fn bitand(self, rhs: Self) -> Self {
330 Self(self.0 & rhs.0)
331 }
332}
333
334impl std::ops::BitXor for StringCompareOptions {
335 type Output = Self;
336 #[inline]
337 fn bitxor(self, rhs: Self) -> Self {
338 Self(self.0 ^ rhs.0)
339 }
340}
341
342impl std::ops::Not for StringCompareOptions {
343 type Output = Self;
344 #[inline]
345 fn not(self) -> Self {
346 Self(!self.0)
347 }
348}
349
350impl std::ops::BitOrAssign for StringCompareOptions {
351 #[inline]
352 fn bitor_assign(&mut self, rhs: Self) {
353 self.0 |= rhs.0;
354 }
355}
356
357impl std::ops::BitAndAssign for StringCompareOptions {
358 #[inline]
359 fn bitand_assign(&mut self, rhs: Self) {
360 self.0 &= rhs.0;
361 }
362}
363
364/// An Objective-C string object.
365///
366/// C++ equivalent: `NS::String`
367#[repr(transparent)]
368#[derive(Clone)]
369pub struct String(NonNull<c_void>);
370
371impl String {
372 /// Create an empty string.
373 ///
374 /// C++ equivalent: `static String* string()`
375 #[inline]
376 pub fn string() -> Option<Self> {
377 unsafe {
378 let ptr: *mut c_void = msg_send_0(class!(NSString).as_ptr(), sel!(string));
379 Self::from_ptr(ptr)
380 }
381 }
382
383 /// Create a string from another string.
384 ///
385 /// C++ equivalent: `static String* string(const String* pString)`
386 #[inline]
387 pub fn string_with_string(string: &String) -> Option<Self> {
388 unsafe {
389 let ptr: *mut c_void = msg_send_1(
390 class!(NSString).as_ptr(),
391 sel!(stringWithString:),
392 string.as_ptr(),
393 );
394 Self::from_ptr(ptr)
395 }
396 }
397
398 /// Create a string from a C string with the specified encoding.
399 ///
400 /// C++ equivalent: `static String* string(const char* pString, StringEncoding encoding)`
401 #[inline]
402 pub fn string_with_cstring(cstring: *const c_char, encoding: StringEncoding) -> Option<Self> {
403 unsafe {
404 let ptr: *mut c_void = msg_send_2(
405 class!(NSString).as_ptr(),
406 sel!(stringWithCString:encoding:),
407 cstring,
408 encoding.0,
409 );
410 Self::from_ptr(ptr)
411 }
412 }
413
414 /// Allocate a new string.
415 ///
416 /// C++ equivalent: `static String* alloc()`
417 #[inline]
418 pub fn alloc() -> Option<Self> {
419 unsafe {
420 let ptr: *mut c_void = msg_send_0(class!(NSString).as_ptr(), sel!(alloc));
421 Self::from_ptr(ptr)
422 }
423 }
424
425 /// Initialize an allocated string.
426 ///
427 /// C++ equivalent: `String* init()`
428 #[inline]
429 pub fn init(&self) -> Option<Self> {
430 unsafe {
431 let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(init));
432 Self::from_ptr(ptr)
433 }
434 }
435
436 /// Initialize with another string.
437 ///
438 /// C++ equivalent: `String* init(const String* pString)`
439 #[inline]
440 pub fn init_with_string(&self, string: &String) -> Option<Self> {
441 unsafe {
442 let ptr: *mut c_void =
443 msg_send_1(self.as_ptr(), sel!(initWithString:), string.as_ptr());
444 Self::from_ptr(ptr)
445 }
446 }
447
448 /// Initialize with a C string and encoding.
449 ///
450 /// C++ equivalent: `String* init(const char* pString, StringEncoding encoding)`
451 #[inline]
452 pub fn init_with_cstring(
453 &self,
454 cstring: *const c_char,
455 encoding: StringEncoding,
456 ) -> Option<Self> {
457 unsafe {
458 let ptr: *mut c_void = msg_send_2(
459 self.as_ptr(),
460 sel!(initWithCString:encoding:),
461 cstring,
462 encoding.0,
463 );
464 Self::from_ptr(ptr)
465 }
466 }
467
468 /// Initialize with bytes, length, encoding, and free buffer flag.
469 ///
470 /// C++ equivalent: `String* init(void* pBytes, UInteger len, StringEncoding encoding, bool freeBuffer)`
471 #[inline]
472 pub fn init_with_bytes_no_copy(
473 &self,
474 bytes: *mut c_void,
475 len: UInteger,
476 encoding: StringEncoding,
477 free_when_done: bool,
478 ) -> Option<Self> {
479 unsafe {
480 let ptr: *mut c_void = msg_send_4(
481 self.as_ptr(),
482 sel!(initWithBytesNoCopy:length:encoding:freeWhenDone:),
483 bytes,
484 len,
485 encoding.0,
486 free_when_done,
487 );
488 Self::from_ptr(ptr)
489 }
490 }
491
492 /// Get the character at the specified index.
493 ///
494 /// C++ equivalent: `unichar character(UInteger index) const`
495 #[inline]
496 pub fn character(&self, index: UInteger) -> Unichar {
497 unsafe { msg_send_1(self.as_ptr(), sel!(characterAtIndex:), index) }
498 }
499
500 /// Get the length of the string in characters.
501 ///
502 /// C++ equivalent: `UInteger length() const`
503 #[inline]
504 pub fn length(&self) -> UInteger {
505 unsafe { msg_send_0(self.as_ptr(), sel!(length)) }
506 }
507
508 /// Get the string as a C string with the specified encoding.
509 ///
510 /// C++ equivalent: `const char* cString(StringEncoding encoding) const`
511 #[inline]
512 pub fn c_string(&self, encoding: StringEncoding) -> *const c_char {
513 unsafe { msg_send_1(self.as_ptr(), sel!(cStringUsingEncoding:), encoding.0) }
514 }
515
516 /// Get the string as a UTF-8 C string.
517 ///
518 /// C++ equivalent: `const char* utf8String() const`
519 #[inline]
520 pub fn utf8_string(&self) -> *const c_char {
521 unsafe { msg_send_0(self.as_ptr(), sel!(UTF8String)) }
522 }
523
524 /// Get the maximum length in bytes for the specified encoding.
525 ///
526 /// C++ equivalent: `UInteger maximumLengthOfBytes(StringEncoding encoding) const`
527 #[inline]
528 pub fn maximum_length_of_bytes(&self, encoding: StringEncoding) -> UInteger {
529 unsafe {
530 msg_send_1(
531 self.as_ptr(),
532 sel!(maximumLengthOfBytesUsingEncoding:),
533 encoding.0,
534 )
535 }
536 }
537
538 /// Get the actual length in bytes for the specified encoding.
539 ///
540 /// C++ equivalent: `UInteger lengthOfBytes(StringEncoding encoding) const`
541 #[inline]
542 pub fn length_of_bytes(&self, encoding: StringEncoding) -> UInteger {
543 unsafe { msg_send_1(self.as_ptr(), sel!(lengthOfBytesUsingEncoding:), encoding.0) }
544 }
545
546 /// Check if this string is equal to another string.
547 ///
548 /// C++ equivalent: `bool isEqualToString(const String* pString) const`
549 #[inline]
550 pub fn is_equal_to_string(&self, string: &String) -> bool {
551 unsafe { msg_send_1(self.as_ptr(), sel!(isEqualToString:), string.as_ptr()) }
552 }
553
554 /// Find the range of a substring with the specified options.
555 ///
556 /// C++ equivalent: `Range rangeOfString(const String* pString, StringCompareOptions options) const`
557 #[inline]
558 pub fn range_of_string(&self, string: &String, options: StringCompareOptions) -> Range {
559 unsafe {
560 msg_send_2(
561 self.as_ptr(),
562 sel!(rangeOfString:options:),
563 string.as_ptr(),
564 options.0,
565 )
566 }
567 }
568
569 /// Get the file system representation of the string.
570 ///
571 /// C++ equivalent: `const char* fileSystemRepresentation() const`
572 #[inline]
573 pub fn file_system_representation(&self) -> *const c_char {
574 unsafe { msg_send_0(self.as_ptr(), sel!(fileSystemRepresentation)) }
575 }
576
577 /// Create a new string by appending another string.
578 ///
579 /// C++ equivalent: `String* stringByAppendingString(const String* pString) const`
580 #[inline]
581 pub fn string_by_appending_string(&self, string: &String) -> Option<Self> {
582 unsafe {
583 let ptr: *mut c_void = msg_send_1(
584 self.as_ptr(),
585 sel!(stringByAppendingString:),
586 string.as_ptr(),
587 );
588 Self::from_ptr(ptr)
589 }
590 }
591
592 /// Perform a case-insensitive comparison with another string.
593 ///
594 /// C++ equivalent: `ComparisonResult caseInsensitiveCompare(const String* pString) const`
595 #[inline]
596 pub fn case_insensitive_compare(&self, string: &String) -> ComparisonResult {
597 unsafe {
598 msg_send_1(
599 self.as_ptr(),
600 sel!(caseInsensitiveCompare:),
601 string.as_ptr(),
602 )
603 }
604 }
605
606 /// Create a String from a raw pointer.
607 ///
608 /// # Safety
609 ///
610 /// The pointer must be a valid Objective-C NSString object.
611 #[inline]
612 pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
613 NonNull::new(ptr).map(Self)
614 }
615
616 // Convenience methods for Rust interop
617
618 /// Create a string from a Rust &str.
619 ///
620 /// This is a convenience method for Rust users.
621 #[inline]
622 pub fn from_str(s: &str) -> Option<Self> {
623 let c_str = std::ffi::CString::new(s).ok()?;
624 Self::string_with_cstring(c_str.as_ptr(), StringEncoding::UTF8)
625 }
626
627 /// Convert to a Rust String.
628 ///
629 /// This is a convenience method for Rust users.
630 #[inline]
631 pub fn to_string(&self) -> Option<std::string::String> {
632 let ptr = self.utf8_string();
633 if ptr.is_null() {
634 return None;
635 }
636 unsafe {
637 let c_str = std::ffi::CStr::from_ptr(ptr);
638 Some(c_str.to_string_lossy().into_owned())
639 }
640 }
641}
642
643impl Referencing for String {
644 #[inline]
645 fn as_ptr(&self) -> *const c_void {
646 self.0.as_ptr()
647 }
648}
649
650impl Copying for String {
651 #[inline]
652 fn copy(&self) -> Option<Self> {
653 unsafe {
654 let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(copy));
655 Self::from_ptr(ptr)
656 }
657 }
658}
659
660// SAFETY: NSString is thread-safe for reference counting
661unsafe impl Send for String {}
662unsafe impl Sync for String {}
663
664impl std::fmt::Debug for String {
665 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
666 match self.to_string() {
667 Some(s) => write!(f, "String({:?})", s),
668 None => write!(f, "String(<invalid>)"),
669 }
670 }
671}
672
673impl std::fmt::Display for String {
674 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
675 match self.to_string() {
676 Some(s) => write!(f, "{}", s),
677 None => write!(f, "<invalid>"),
678 }
679 }
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 #[test]
687 fn test_string_encoding_values() {
688 assert_eq!(StringEncoding::ASCII.0, 1);
689 assert_eq!(StringEncoding::UTF8.0, 4);
690 assert_eq!(StringEncoding::UNICODE.0, 10);
691 assert_eq!(StringEncoding::UTF16.0, 10);
692 assert_eq!(StringEncoding::UTF32.0, 0x8c000100);
693 }
694
695 #[test]
696 fn test_string_compare_options() {
697 let options = StringCompareOptions::CASE_INSENSITIVE | StringCompareOptions::BACKWARDS;
698 assert!(options.contains(StringCompareOptions::CASE_INSENSITIVE));
699 assert!(options.contains(StringCompareOptions::BACKWARDS));
700 assert!(!options.contains(StringCompareOptions::LITERAL));
701 }
702}