1use core::convert::TryFrom;
2use core::fmt::Write as _;
3use core::hash::BuildHasher;
4use core::str;
5
6use artichoke_core::hash::Hash as _;
7use artichoke_core::value::Value as _;
8use bstr::ByteSlice;
9
10use super::Encoding;
11use crate::convert::implicitly_convert_to_int;
12use crate::convert::implicitly_convert_to_nilable_string;
13use crate::convert::implicitly_convert_to_spinoso_string;
14use crate::convert::implicitly_convert_to_string;
15use crate::extn::core::array::Array;
16#[cfg(feature = "core-regexp")]
17use crate::extn::core::matchdata::{self, MatchData};
18#[cfg(feature = "core-regexp")]
19use crate::extn::core::regexp::{self, Regexp};
20use crate::extn::core::symbol::Symbol;
21use crate::extn::prelude::*;
22use crate::sys::protect;
23
24pub fn mul(interp: &mut Artichoke, mut value: Value, count: Value) -> Result<Value, Error> {
25 let count = implicitly_convert_to_int(interp, count)?;
26 let count = usize::try_from(count).map_err(|_| ArgumentError::with_message("negative argument"))?;
27
28 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
29 if count.checked_mul(s.len()).is_none() {
31 return Err(RangeError::with_message("bignum too big to convert into `long'").into());
32 }
33 let repeated_s = s.as_slice().repeat(count).into();
34 super::String::alloc_value(repeated_s, interp)
35}
36
37pub fn add(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
38 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
39 let to_append = unsafe { implicitly_convert_to_string(interp, &mut other)? };
42
43 let mut concatenated = s.clone();
44 concatenated.extend_from_slice(to_append);
47 super::String::alloc_value(concatenated, interp)
48}
49
50pub fn append(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
51 check_frozen(interp, value)?;
52
53 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
54 if let Ok(int) = other.try_convert_into::<i64>(interp) {
55 unsafe {
58 let string_mut = s.as_inner_mut();
59 string_mut
62 .try_push_int(int)
63 .map_err(|err| RangeError::from(err.message()))?;
64 let s = s.take();
65 return super::String::box_into_value(s, value, interp);
66 }
67 }
68 let other = unsafe { implicitly_convert_to_spinoso_string(interp, &mut other)? };
71 match s.encoding() {
72 Encoding::Utf8 => {
121 unsafe {
124 let string_mut = s.as_inner_mut();
125 string_mut.extend_from_slice(other.as_slice());
128
129 if !matches!(other.encoding(), Encoding::Utf8) && !other.is_ascii_only() {
130 string_mut.set_encoding(other.encoding());
132 }
133
134 let s = s.take();
135 super::String::box_into_value(s, value, interp)
136 }
137 }
138 Encoding::Ascii if s.is_empty() => {
169 unsafe {
172 let string_mut = s.as_inner_mut();
173 string_mut.extend_from_slice(other.as_slice());
176
177 if !other.is_ascii_only() {
179 string_mut.set_encoding(other.encoding());
180 }
181
182 let s = s.take();
183 super::String::box_into_value(s, value, interp)
184 }
185 }
186 Encoding::Ascii => {
212 if !other.is_ascii() {
213 let code = format!(
214 "raise Encoding::CompatibilityError, 'incompatible character encodings: {} and {}",
215 s.encoding(),
216 other.encoding()
217 );
218 interp.eval(code.as_bytes())?;
219 unreachable!("raised exception");
220 }
221 unsafe {
224 let string_mut = s.as_inner_mut();
225 string_mut.extend_from_slice(other.as_slice());
228
229 let s = s.take();
230 super::String::box_into_value(s, value, interp)
231 }
232 }
233 Encoding::Binary if s.is_empty() => {
266 unsafe {
269 let string_mut = s.as_inner_mut();
270 string_mut.extend_from_slice(other.as_slice());
273
274 if !other.is_ascii_only() {
275 string_mut.set_encoding(other.encoding());
276 }
277
278 let s = s.take();
279 super::String::box_into_value(s, value, interp)
280 }
281 }
282 Encoding::Binary => {
283 unsafe {
286 let string_mut = s.as_inner_mut();
287 string_mut.extend_from_slice(other.as_slice());
290
291 let s = s.take();
292 super::String::box_into_value(s, value, interp)
293 }
294 }
295 }
296}
297
298pub fn cmp_rocket(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
299 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
300 if let Ok(other) = unsafe { super::String::unbox_from_value(&mut other, interp) } {
301 let cmp = s.cmp(&*other);
302 Ok(interp.convert(cmp as i64))
303 } else {
304 Ok(Value::nil())
305 }
306}
307
308pub fn equals_equals(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
309 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
310 if let Ok(other) = unsafe { super::String::unbox_from_value(&mut other, interp) } {
311 let equals = *s == *other;
312 return Ok(interp.convert(equals));
313 }
314 if value.respond_to(interp, "to_str")? {
315 let result = other.funcall(interp, "==", &[value], None)?;
316 if let Ok(result) = result.try_convert_into::<Option<bool>>(interp) {
318 let result = result.unwrap_or_default();
319 Ok(interp.convert(result))
320 } else {
321 Ok(interp.convert(true))
322 }
323 } else {
324 Ok(interp.convert(false))
325 }
326}
327
328#[allow(unused_mut)]
329#[expect(
330 clippy::cast_possible_wrap,
331 reason = "mruby stores sizes as int64_t instead of size_t"
332)]
333pub fn aref(
334 interp: &mut Artichoke,
335 mut value: Value,
336 mut first: Value,
337 second: Option<Value>,
338) -> Result<Value, Error> {
339 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
340 if let Some(second) = second {
341 #[cfg(feature = "core-regexp")]
342 if let Ok(regexp) = unsafe { Regexp::unbox_from_value(&mut first, interp) } {
343 let match_data = regexp.match_(interp, Some(s.as_slice()), None, None)?;
344 if match_data.is_nil() {
345 return Ok(Value::nil());
346 }
347 return matchdata::trampoline::element_reference(interp, match_data, second, None);
348 }
349 let index = implicitly_convert_to_int(interp, first)?;
350 let length = implicitly_convert_to_int(interp, second)?;
351
352 let index = match aref::offset_to_index(index, s.len()) {
353 None => return Ok(Value::nil()),
354 Some(index) if index > s.len() => return Ok(Value::nil()),
370 Some(index) => index,
371 };
372
373 if let Ok(length) = usize::try_from(length) {
380 let end = index
391 .checked_add(length)
392 .ok_or_else(|| RangeError::with_message("bignum too big to convert into `long'"))?;
393 if let Some(slice) = s.get_char_slice(index..end) {
394 let s = super::String::with_bytes_and_encoding(slice.to_vec(), s.encoding());
409 return super::String::alloc_value(s, interp);
431 }
432 }
433 return Ok(Value::nil());
434 }
435 #[cfg(feature = "core-regexp")]
436 if let Ok(regexp) = unsafe { Regexp::unbox_from_value(&mut first, interp) } {
437 let match_data = regexp.match_(interp, Some(s.as_slice()), None, None)?;
438 if match_data.is_nil() {
439 return Ok(Value::nil());
440 }
441 return matchdata::trampoline::element_reference(interp, match_data, interp.convert(0), None);
442 }
443 match first.is_range(interp, s.char_len() as i64)? {
444 None => {}
445 Some(protect::Range::Out) => return Ok(Value::nil()),
450 Some(protect::Range::Valid { start: index, len }) => {
451 let index = match aref::offset_to_index(index, s.len()) {
452 None => return Ok(Value::nil()),
453 Some(index) if index > s.len() => return Ok(Value::nil()),
454 Some(index) => index,
455 };
456 if let Ok(length) = usize::try_from(len) {
457 let end = index
458 .checked_add(length)
459 .ok_or_else(|| RangeError::with_message("bignum too big to convert into `long'"))?;
460 if let Some(slice) = s.get_char_slice(index..end) {
461 let s = super::String::with_bytes_and_encoding(slice.to_vec(), s.encoding());
462 return super::String::alloc_value(s, interp);
463 }
464 }
465 return Ok(Value::nil());
466 }
467 }
468 if let Ok(substring) = unsafe { super::String::unbox_from_value(&mut first, interp) } {
486 if s.index(&*substring, None).is_some() {
487 let encoding = substring.encoding();
526 let s = super::String::with_bytes_and_encoding(substring.to_vec(), encoding);
527 return super::String::alloc_value(s, interp);
528 }
529 return Ok(Value::nil());
530 }
531 let index = implicitly_convert_to_int(interp, first)?;
532
533 if let Some(index) = aref::offset_to_index(index, s.len()) {
534 if let Some(bytes) = s.get_char(index) {
547 let s = super::String::with_bytes_and_encoding(bytes.to_vec(), s.encoding());
548 return super::String::alloc_value(s, interp);
549 }
550 }
551 Ok(Value::nil())
552}
553
554pub fn aset(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
555 check_frozen(interp, value)?;
556
557 let _s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
558 Err(NotImplementedError::new().into())
559}
560
561pub fn is_ascii_only(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
562 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
563 let is_ascii_only = s.is_ascii_only();
564 Ok(interp.convert(is_ascii_only))
565}
566
567pub fn b(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
568 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
569 super::String::alloc_value(s.to_binary(), interp)
570}
571
572pub fn byteindex(
573 interp: &mut Artichoke,
574 mut value: Value,
575 mut substring: Value,
576 offset: Option<Value>,
577) -> Result<Value, Error> {
578 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
579 #[cfg(feature = "core-regexp")]
580 if let Ok(pattern) = unsafe { Regexp::unbox_from_value(&mut substring, interp) } {
581 let offset_number = if let Some(offset) = offset {
582 Some(implicitly_convert_to_int(interp, offset)?)
583 } else {
584 None
585 };
586
587 let mut matches = pattern.match_(interp, Some(s.as_slice()), offset_number, None)?;
591 if matches.is_nil() {
592 return Ok(Value::nil());
593 }
594 let first_ocurrence = unsafe { MatchData::unbox_from_value(&mut matches, interp)? };
595 let ocurrence_index = first_ocurrence.begin(matchdata::Capture::GroupIndex(0))?;
596 match ocurrence_index {
597 Some(n) => return interp.try_convert(n),
598 None => return Ok(Value::nil()),
599 }
600 }
601 let needle = unsafe { implicitly_convert_to_string(interp, &mut substring)? };
602 let offset = if let Some(offset) = offset {
603 let offset = implicitly_convert_to_int(interp, offset)?;
604 match aref::offset_to_index(offset, s.len()) {
605 None => return Ok(Value::nil()),
606 Some(offset) if offset > s.len() => return Ok(Value::nil()),
607 Some(offset) => Some(offset),
608 }
609 } else {
610 None
611 };
612 interp.try_convert(s.byteindex(needle, offset))
613}
614
615pub fn byterindex(
616 interp: &mut Artichoke,
617 mut value: Value,
618 mut substring: Value,
619 offset: Option<Value>,
620) -> Result<Value, Error> {
621 #[cfg(feature = "core-regexp")]
622 if let Ok(_pattern) = unsafe { Regexp::unbox_from_value(&mut substring, interp) } {
623 return Err(NotImplementedError::from("String#byterindex with Regexp pattern").into());
624 }
625 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
626 let needle = unsafe { implicitly_convert_to_string(interp, &mut substring)? };
627 let offset = if let Some(offset) = offset {
628 let offset = implicitly_convert_to_int(interp, offset)?;
629 match aref::offset_to_index(offset, s.len()) {
630 None => return Ok(Value::nil()),
631 Some(offset) if offset > s.len() => return Ok(Value::nil()),
632 Some(offset) => Some(offset),
633 }
634 } else {
635 None
636 };
637 interp.try_convert(s.byterindex(needle, offset))
638}
639
640pub fn bytes(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
641 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
642 let bytes = s
643 .bytes()
644 .map(i64::from)
645 .map(|byte| interp.convert(byte))
646 .collect::<Array>();
647 Array::alloc_value(bytes, interp)
648}
649
650pub fn bytesize(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
651 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
652 let bytesize = s.bytesize();
653 interp.try_convert(bytesize)
654}
655
656#[expect(
657 clippy::cast_possible_wrap,
658 reason = "mruby stores sizes as int64_t instead of size_t"
659)]
660pub fn byteslice(
661 interp: &mut Artichoke,
662 mut value: Value,
663 index: Value,
664 length: Option<Value>,
665) -> Result<Value, Error> {
666 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
667 let maybe_range = if length.is_none() {
668 index.is_range(interp, s.bytesize() as i64)?
669 } else {
670 None
671 };
672 match maybe_range {
673 None => {}
674 Some(protect::Range::Out) => return Ok(Value::nil()),
675 Some(protect::Range::Valid { start: index, len }) => {
676 let index = match aref::offset_to_index(index, s.len()) {
677 None => return Ok(Value::nil()),
678 Some(index) if index > s.len() => return Ok(Value::nil()),
679 Some(index) => index,
680 };
681 if let Ok(length) = usize::try_from(len) {
682 let end = index
683 .checked_add(length)
684 .ok_or_else(|| RangeError::with_message("bignum too big to convert into `long'"))?;
685 if let Some(slice) = s.get(index..end).or_else(|| s.get(index..)) {
686 let s = super::String::with_bytes_and_encoding(slice.to_vec(), s.encoding());
701 return super::String::alloc_value(s, interp);
723 }
724 }
725 }
726 }
727 let index = implicitly_convert_to_int(interp, index)?;
742
743 let index = match aref::offset_to_index(index, s.len()) {
744 None => return Ok(Value::nil()),
745 Some(index) if index > s.len() => return Ok(Value::nil()),
761 Some(index) => index,
762 };
763
764 let length = if let Some(length) = length {
765 length
766 } else {
767 if let Some(&byte) = s.get(index) {
774 let s = super::String::with_bytes_and_encoding(vec![byte], s.encoding());
775 return super::String::alloc_value(s, interp);
797 }
798 return Ok(Value::nil());
799 };
800
801 let length = implicitly_convert_to_int(interp, length)?;
818
819 if let Ok(length) = usize::try_from(length) {
826 let end = index
837 .checked_add(length)
838 .ok_or_else(|| RangeError::with_message("bignum too big to convert into `long'"))?;
839 if let Some(slice) = s.get(index..end).or_else(|| s.get(index..)) {
840 let s = super::String::with_bytes_and_encoding(slice.to_vec(), s.encoding());
855 return super::String::alloc_value(s, interp);
877 }
878 }
879 Ok(Value::nil())
880}
881
882pub fn capitalize(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
883 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
884 let mut dup = s.clone();
885 dup.make_capitalized();
886 super::String::alloc_value(dup, interp)
887}
888
889pub fn capitalize_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
890 check_frozen(interp, value)?;
891
892 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
893 let (effect, returned) = unsafe {
896 let string_mut = s.as_inner_mut();
897 let effect = string_mut.make_capitalized();
900
901 let s = s.take();
902 (effect, super::String::box_into_value(s, value, interp)?)
903 };
904 if effect.changed() {
905 Ok(returned)
906 } else {
907 Ok(Value::nil())
908 }
909}
910
911pub fn casecmp_ascii(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
912 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
913 if let Ok(other) = unsafe { implicitly_convert_to_string(interp, &mut other) } {
916 let cmp = s.ascii_casecmp(other) as i64;
917 Ok(interp.convert(cmp))
918 } else {
919 Ok(Value::nil())
920 }
921}
922
923pub fn casecmp_unicode(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
924 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
925 if let Ok(other) = unsafe { super::String::unbox_from_value(&mut other, interp) } {
927 let eql = *s == *other;
928 Ok(interp.convert(eql))
929 } else {
930 Ok(interp.convert(false))
931 }
932}
933
934pub fn center(interp: &mut Artichoke, mut value: Value, width: Value, padstr: Option<Value>) -> Result<Value, Error> {
935 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
936 let width = implicitly_convert_to_int(interp, width)?;
937 let width = if let Ok(width) = usize::try_from(width) {
938 width
939 } else {
940 let dup = s.clone();
953 return super::String::alloc_value(dup, interp);
954 };
955 let padstr = if let Some(mut padstr) = padstr {
958 let padstr = unsafe { implicitly_convert_to_string(interp, &mut padstr)? };
959 Some(padstr.to_vec())
960 } else {
961 None
962 };
963 let centered = s
964 .center(width, padstr.as_deref())
965 .map_err(|e| ArgumentError::with_message(e.message()))?
966 .collect::<super::String>();
967 super::String::alloc_value(centered, interp)
968}
969
970pub fn chars(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
971 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
972 let chars = s.chars().collect::<Vec<&[u8]>>();
973 interp.try_convert_mut(chars)
974}
975
976pub fn chomp(interp: &mut Artichoke, mut value: Value, separator: Option<Value>) -> Result<Value, Error> {
977 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
978 let mut dup = s.clone();
979 if let Some(mut separator) = separator {
980 if let Some(sep) = unsafe { implicitly_convert_to_nilable_string(interp, &mut separator)? } {
981 let _ = dup.chomp(Some(sep));
982 } else {
983 return interp.try_convert_mut("");
984 }
985 } else {
986 let _ = dup.chomp(None::<&[u8]>);
987 }
988 super::String::alloc_value(dup, interp)
989}
990
991pub fn chomp_bang(interp: &mut Artichoke, mut value: Value, separator: Option<Value>) -> Result<Value, Error> {
992 check_frozen(interp, value)?;
993
994 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
995 unsafe {
996 let string_mut = s.as_inner_mut();
997 let modified = if let Some(mut separator) = separator {
998 if let Some(sep) = implicitly_convert_to_nilable_string(interp, &mut separator)? {
999 string_mut.chomp(Some(sep))
1000 } else {
1001 return Ok(Value::nil());
1002 }
1003 } else {
1004 string_mut.chomp(None::<&[u8]>)
1005 };
1006 if modified {
1007 let s = s.take();
1008 return super::String::box_into_value(s, value, interp);
1009 }
1010 }
1011 Ok(Value::nil())
1012}
1013
1014pub fn chop(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1015 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1016 let mut dup = s.clone();
1017 let _ = dup.chop();
1018 super::String::alloc_value(dup, interp)
1019}
1020
1021pub fn chop_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1022 check_frozen(interp, value)?;
1023
1024 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1025 if s.is_empty() {
1026 return Ok(Value::nil());
1027 }
1028 unsafe {
1029 let string_mut = s.as_inner_mut();
1030 let modified = string_mut.chop();
1031 if modified {
1032 let s = s.take();
1033 return super::String::box_into_value(s, value, interp);
1034 }
1035 }
1036 Ok(value)
1037}
1038
1039pub fn chr(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1040 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1041 let chr = s.chr();
1042 interp.try_convert_mut(chr)
1043}
1044
1045pub fn clear(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1046 check_frozen(interp, value)?;
1047
1048 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1049 unsafe {
1052 let string_mut = s.as_inner_mut();
1053 string_mut.clear();
1054
1055 let s = s.take();
1056 super::String::box_into_value(s, value, interp)
1057 }
1058}
1059
1060pub fn codepoints(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1061 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1062 let codepoints = s
1063 .codepoints()
1064 .map_err(|err| ArgumentError::with_message(err.message()))?;
1065 let codepoints = codepoints.map(|ch| interp.convert(ch)).collect::<Array>();
1066 Array::alloc_value(codepoints, interp)
1067}
1068
1069pub fn downcase(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1070 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1071 let mut dup = s.clone();
1072 dup.make_lowercase();
1073 super::String::alloc_value(dup, interp)
1074}
1075
1076pub fn downcase_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1077 check_frozen(interp, value)?;
1078
1079 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1080 let (effect, returned) = unsafe {
1083 let string_mut = s.as_inner_mut();
1084 let effect = string_mut.make_lowercase();
1087
1088 let s = s.take();
1089 (effect, super::String::box_into_value(s, value, interp)?)
1090 };
1091 if effect.changed() {
1092 Ok(returned)
1093 } else {
1094 Ok(Value::nil())
1095 }
1096}
1097
1098pub fn is_empty(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1099 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1100 Ok(interp.convert(s.is_empty()))
1101}
1102
1103pub fn end_with<T>(interp: &mut Artichoke, mut value: Value, suffixes: T) -> Result<Value, Error>
1104where
1105 T: IntoIterator<Item = Value>,
1106{
1107 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1108
1109 for mut suffix in suffixes {
1110 let needle = unsafe { implicitly_convert_to_string(interp, &mut suffix)? };
1113 if s.ends_with(needle) {
1114 return Ok(interp.convert(true));
1115 }
1116 }
1117 Ok(interp.convert(false))
1118}
1119
1120pub fn eql(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
1121 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1122 if let Ok(other) = unsafe { super::String::unbox_from_value(&mut other, interp) } {
1123 let eql = *s == *other;
1124 Ok(interp.convert(eql))
1125 } else {
1126 Ok(interp.convert(false))
1127 }
1128}
1129
1130pub fn getbyte(interp: &mut Artichoke, mut value: Value, index: Value) -> Result<Value, Error> {
1131 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1132 let index = implicitly_convert_to_int(interp, index)?;
1133 let index = if let Some(index) = aref::offset_to_index(index, s.len()) {
1134 index
1135 } else {
1136 return Ok(Value::nil());
1137 };
1138 let byte = s.get(index).copied().map(i64::from);
1139 Ok(interp.convert(byte))
1140}
1141
1142pub fn hash(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1143 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1144 let hash = interp.global_build_hasher()?.hash_one(s.as_slice());
1145 let hash = i64::from_ne_bytes(hash.to_ne_bytes());
1148 Ok(interp.convert(hash))
1149}
1150
1151pub fn include(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
1152 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1153 let other_str = unsafe { implicitly_convert_to_string(interp, &mut other)? };
1154 let includes = s.index(other_str, None).is_some();
1155 Ok(interp.convert(includes))
1156}
1157
1158pub fn index(
1159 interp: &mut Artichoke,
1160 mut value: Value,
1161 mut needle: Value,
1162 offset: Option<Value>,
1163) -> Result<Value, Error> {
1164 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1165 #[cfg(feature = "core-regexp")]
1166 if let Ok(_pattern) = unsafe { Regexp::unbox_from_value(&mut needle, interp) } {
1167 return Err(NotImplementedError::from("String#index with Regexp pattern").into());
1168 }
1169 let needle = unsafe { implicitly_convert_to_string(interp, &mut needle)? };
1170 let index = if let Some(offset) = offset {
1171 let offset = implicitly_convert_to_int(interp, offset)?;
1172 let offset = aref::offset_to_index(offset, s.len());
1173 s.index(needle, offset)
1174 } else {
1175 s.index(needle, None)
1176 };
1177 let index = index.and_then(|index| i64::try_from(index).ok());
1178 interp.try_convert(index)
1179}
1180
1181pub fn initialize(interp: &mut Artichoke, mut value: Value, from: Option<Value>) -> Result<Value, Error> {
1182 if from.is_some() {
1183 check_frozen(interp, value)?;
1184 }
1185
1186 let Some(mut from) = from else {
1187 return Ok(value);
1192 };
1193
1194 let from = unsafe { implicitly_convert_to_string(interp, &mut from)? };
1217 let buf = from.to_vec();
1218 if let Ok(s) = unsafe { super::String::unbox_from_value(&mut value, interp) } {
1232 unsafe {
1233 let inner = s.take();
1234 drop(inner);
1235 }
1236 }
1237 let buf = super::String::from(buf);
1240 super::String::box_into_value(buf, value, interp)
1241}
1242
1243pub fn initialize_copy(interp: &mut Artichoke, mut value: Value, mut other: Value) -> Result<Value, Error> {
1244 check_frozen(interp, value)?;
1245
1246 let buf = unsafe {
1249 let from = implicitly_convert_to_string(interp, &mut other)?;
1250 from.to_vec()
1251 };
1252 if let Ok(s) = unsafe { super::String::unbox_from_value(&mut value, interp) } {
1255 unsafe {
1256 let inner = s.take();
1257 drop(inner);
1258 }
1259 }
1260 let replacement = super::String::with_bytes_and_encoding(buf, Encoding::Utf8);
1262 super::String::box_into_value(replacement, value, interp)
1263}
1264
1265pub fn inspect(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1266 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1267 let inspect = s.inspect().collect::<super::String>();
1268 super::String::alloc_value(inspect, interp)
1269}
1270
1271pub fn intern(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1272 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1273 let bytes = s.as_slice();
1274 let sym = if let Some(sym) = interp.check_interned_bytes(bytes)? {
1275 sym
1276 } else {
1277 interp.intern_bytes(bytes.to_vec())?
1278 };
1279 Symbol::alloc_value(sym.into(), interp)
1280}
1281
1282pub fn length(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1283 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1284 let length = s.char_len();
1285 interp.try_convert(length)
1286}
1287
1288pub fn ord(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1289 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1290 let ord = s.ord().map_err(|err| ArgumentError::with_message(err.message()))?;
1291 Ok(interp.convert(ord))
1292}
1293
1294pub fn replace(interp: &mut Artichoke, value: Value, other: Value) -> Result<Value, Error> {
1295 initialize_copy(interp, value, other)
1296}
1297
1298pub fn reverse(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1299 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1300 let mut reversed = s.clone();
1301 reversed.reverse();
1302 super::String::alloc_value(reversed, interp)
1303}
1304
1305pub fn reverse_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1306 check_frozen(interp, value)?;
1307
1308 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1309 unsafe {
1312 let string_mut = s.as_inner_mut();
1313 string_mut.reverse();
1314
1315 let s = s.take();
1316 super::String::box_into_value(s, value, interp)
1317 }
1318}
1319
1320pub fn rindex(
1321 interp: &mut Artichoke,
1322 mut value: Value,
1323 mut needle: Value,
1324 offset: Option<Value>,
1325) -> Result<Value, Error> {
1326 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1327 #[cfg(feature = "core-regexp")]
1328 if let Ok(_pattern) = unsafe { Regexp::unbox_from_value(&mut needle, interp) } {
1329 return Err(NotImplementedError::from("String#index with Regexp pattern").into());
1330 }
1331 let needle = unsafe { implicitly_convert_to_string(interp, &mut needle)? };
1332 let index = if let Some(offset) = offset {
1333 let offset = implicitly_convert_to_int(interp, offset)?;
1334 let offset = aref::offset_to_index(offset, s.len());
1335 s.rindex(needle, offset)
1336 } else {
1337 s.rindex(needle, None)
1338 };
1339 let index = index.and_then(|index| i64::try_from(index).ok());
1340 interp.try_convert(index)
1341}
1342
1343pub fn scan(interp: &mut Artichoke, value: Value, mut pattern: Value, block: Option<Block>) -> Result<Value, Error> {
1344 if let Ruby::Symbol = pattern.ruby_type() {
1345 let mut message = String::from("wrong argument type ");
1346 message.push_str(interp.inspect_type_name_for_value(pattern));
1347 message.push_str(" (expected Regexp)");
1348 return Err(TypeError::from(message).into());
1349 }
1350 #[cfg(feature = "core-regexp")]
1351 if let Ok(regexp) = unsafe { Regexp::unbox_from_value(&mut pattern, interp) } {
1352 let haystack = value.try_convert_into_mut::<&[u8]>(interp)?;
1353 let scan = regexp.inner().scan(interp, haystack, block)?;
1354 return Ok(interp.try_convert_mut(scan)?.unwrap_or(value));
1355 }
1356 #[cfg(feature = "core-regexp")]
1357 if let Ok(pattern_bytes) = unsafe { implicitly_convert_to_string(interp, &mut pattern) } {
1360 let pattern_bytes = pattern_bytes.to_vec();
1361
1362 let string = value.try_convert_into_mut::<&[u8]>(interp)?;
1363 if let Some(ref block) = block {
1364 let regex = Regexp::try_from(pattern_bytes.clone())?;
1365 let matchdata = MatchData::new(string.to_vec(), regex, ..);
1366 let patlen = pattern_bytes.len();
1367 if let Some(pos) = string.find(&pattern_bytes) {
1368 let mut data = matchdata.clone();
1369 data.set_region(pos..pos + patlen);
1370 let data = MatchData::alloc_value(data, interp)?;
1371 interp.set_global_variable(regexp::LAST_MATCH, &data)?;
1372
1373 let block_arg = interp.try_convert_mut(pattern_bytes.as_slice())?;
1374 block.yield_arg(interp, &block_arg)?;
1375
1376 interp.set_global_variable(regexp::LAST_MATCH, &data)?;
1377
1378 let offset = pos + patlen;
1379 let string = string.get(offset..).unwrap_or_default();
1380 for pos in string.find_iter(&pattern_bytes) {
1381 let mut data = matchdata.clone();
1382 data.set_region(offset + pos..offset + pos + patlen);
1383 let data = MatchData::alloc_value(data, interp)?;
1384 interp.set_global_variable(regexp::LAST_MATCH, &data)?;
1385
1386 let block_arg = interp.try_convert_mut(pattern_bytes.as_slice())?;
1387 block.yield_arg(interp, &block_arg)?;
1388
1389 interp.set_global_variable(regexp::LAST_MATCH, &data)?;
1390 }
1391 } else {
1392 interp.unset_global_variable(regexp::LAST_MATCH)?;
1393 }
1394 return Ok(value);
1395 }
1396 let (matches, last_pos) = string
1397 .find_iter(&pattern_bytes)
1398 .enumerate()
1399 .last()
1400 .map(|(m, p)| (m + 1, p))
1401 .unwrap_or_default();
1402 let mut result = Vec::with_capacity(matches);
1403 for _ in 0..matches {
1404 result.push(interp.try_convert_mut(pattern_bytes.as_slice())?);
1405 }
1406 if matches > 0 {
1407 let regex = Regexp::try_from(pattern_bytes.clone())?;
1408 let matchdata = MatchData::new(string.to_vec(), regex, last_pos..last_pos + pattern_bytes.len());
1409 let data = MatchData::alloc_value(matchdata, interp)?;
1410 interp.set_global_variable(regexp::LAST_MATCH, &data)?;
1411 } else {
1412 interp.unset_global_variable(regexp::LAST_MATCH)?;
1413 }
1414 return interp.try_convert_mut(result);
1415 }
1416 #[cfg(not(feature = "core-regexp"))]
1417 if let Ok(pattern_bytes) = unsafe { implicitly_convert_to_string(interp, &mut pattern) } {
1420 let pattern_bytes = pattern_bytes.to_vec();
1421
1422 let string = value.try_convert_into_mut::<&[u8]>(interp)?;
1423 if let Some(ref block) = block {
1424 let patlen = pattern_bytes.len();
1425 if let Some(pos) = string.find(&pattern_bytes) {
1426 let block_arg = interp.try_convert_mut(pattern_bytes.as_slice())?;
1427 block.yield_arg(interp, &block_arg)?;
1428
1429 let offset = pos + patlen;
1430 let string = string.get(offset..).unwrap_or_default();
1431 for _ in string.find_iter(&pattern_bytes) {
1432 let block_arg = interp.try_convert_mut(pattern_bytes.as_slice())?;
1433 block.yield_arg(interp, &block_arg)?;
1434 }
1435 }
1436 return Ok(value);
1437 }
1438 let matches = string
1439 .find_iter(&pattern_bytes)
1440 .enumerate()
1441 .last()
1442 .map(|(m, _)| m + 1)
1443 .unwrap_or_default();
1444 let mut result = Vec::with_capacity(matches);
1445 for _ in 0..matches {
1446 result.push(interp.try_convert_mut(pattern_bytes.as_slice())?);
1447 }
1448 return interp.try_convert_mut(result);
1449 }
1450 let mut message = String::from("wrong argument type ");
1451 message.push_str(interp.inspect_type_name_for_value(pattern));
1452 message.push_str(" (expected Regexp)");
1453 Err(TypeError::from(message).into())
1454}
1455
1456pub fn setbyte(interp: &mut Artichoke, mut value: Value, index: Value, byte: Value) -> Result<Value, Error> {
1457 let index = implicitly_convert_to_int(interp, index)?;
1458 let i64_byte = implicitly_convert_to_int(interp, byte)?;
1459
1460 check_frozen(interp, value)?;
1461
1462 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1463 let index = if let Some(index) = aref::offset_to_index(index, s.len()) {
1464 index
1465 } else {
1466 let mut message = String::from("index ");
1467 let _ignored = write!(&mut message, "{index} out of string");
1470 return Err(IndexError::from(message).into());
1471 };
1472 let u8_byte = (i64_byte % 256)
1499 .try_into()
1500 .expect("taking mod 256 guarantees the resulting i64 is in range for u8");
1501 unsafe {
1503 let string_mut = s.as_inner_mut();
1504 let cell = string_mut.get_mut(index).ok_or_else(|| {
1505 let mut message = String::from("index ");
1506 let _ignored = write!(&mut message, "{index} out of string");
1509 IndexError::from(message)
1510 })?;
1511 *cell = u8_byte;
1512 }
1513
1514 Ok(byte)
1516}
1517
1518pub fn slice_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1519 let _s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1520 Err(NotImplementedError::new().into())
1521}
1522
1523pub fn split(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1524 let _s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1525 Err(NotImplementedError::new().into())
1526}
1527
1528pub fn start_with<T>(interp: &mut Artichoke, mut value: Value, prefixes: T) -> Result<Value, Error>
1529where
1530 T: IntoIterator<Item = Value>,
1531{
1532 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1533
1534 for mut prefix in prefixes {
1535 if prefix.ruby_type() == Ruby::String {
1536 let needle = unsafe { super::String::unbox_from_value(&mut prefix, interp)? };
1537 if s.starts_with(needle.as_inner_ref()) {
1538 return Ok(interp.convert(true));
1539 }
1540 } else {
1541 #[cfg(feature = "core-regexp")]
1542 if let Ok(regexp) = unsafe { Regexp::unbox_from_value(&mut prefix, interp) } {
1543 let mut inner = regexp.inner().match_(interp, &s, None, None)?;
1544 if inner.is_nil() {
1545 continue;
1546 }
1547
1548 let match_data = unsafe { MatchData::unbox_from_value(&mut inner, interp)? };
1549 if match_data.begin(matchdata::Capture::GroupIndex(0))? == Some(0) {
1550 return Ok(interp.convert(true));
1551 }
1552
1553 regexp::clear_capture_globals(interp)?;
1554 interp.unset_global_variable(regexp::LAST_MATCH)?;
1555 interp.unset_global_variable(regexp::STRING_LEFT_OF_MATCH)?;
1556 interp.unset_global_variable(regexp::STRING_RIGHT_OF_MATCH)?;
1557 continue;
1558 }
1559
1560 let needle = unsafe { implicitly_convert_to_string(interp, &mut prefix)? };
1563 if s.starts_with(needle) {
1564 return Ok(interp.convert(true));
1565 }
1566 }
1567 }
1568
1569 Ok(interp.convert(false))
1570}
1571
1572pub fn swapcase(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1573 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1574 let mut dup = s.clone();
1575 dup.make_swapcase();
1576 super::String::alloc_value(dup, interp)
1577}
1578
1579pub fn swapcase_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1580 check_frozen(interp, value)?;
1581
1582 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1583 let (effect, returned) = unsafe {
1586 let string_mut = s.as_inner_mut();
1587 let effect = string_mut.make_swapcase();
1590
1591 let s = s.take();
1592 (effect, super::String::box_into_value(s, value, interp)?)
1593 };
1594 if effect.changed() {
1595 Ok(returned)
1596 } else {
1597 Ok(Value::nil())
1598 }
1599}
1600
1601pub fn to_f(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1602 let _s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1603 Err(NotImplementedError::new().into())
1604}
1605
1606pub fn to_i(interp: &mut Artichoke, mut value: Value, base: Option<Value>) -> Result<Value, Error> {
1607 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1608 let mut slice = s.as_slice();
1609 if let Some(start) = slice.iter().position(|&c| !posix_space::is_space(c)) {
1611 slice = &slice[start..];
1612 } else {
1613 slice = &[];
1615 }
1616
1617 let sign = match slice.first() {
1619 Some(b'+') => {
1620 slice = &slice[1..];
1621 1
1622 }
1623 Some(b'-') => {
1624 slice = &slice[1..];
1625 -1
1626 }
1627 _ => 1,
1628 };
1629
1630 let base = base.map_or(Ok(10), |b| implicitly_convert_to_int(interp, b))?;
1631 let base = match base {
1632 x if x < 0 || x == 1 || x > 36 => return Err(ArgumentError::from(format!("invalid radix {base}")).into()),
1633 0 => {
1634 if slice.len() < 2 || slice[0] != b'0' {
1636 10
1637 } else {
1638 match &slice[1] {
1639 b'b' | b'B' => {
1640 slice = &slice[2..];
1641 2
1642 }
1643 b'o' | b'O' => {
1644 slice = &slice[2..];
1645 8
1646 }
1647 b'd' | b'D' => {
1648 slice = &slice[2..];
1649 10
1650 }
1651 b'x' | b'X' => {
1652 slice = &slice[2..];
1653 16
1654 }
1655 _ => {
1656 slice = &slice[1..];
1658 8
1659 }
1660 }
1661 }
1662 }
1663 x => {
1664 if slice.len() >= 2
1666 && matches!(
1667 (x, &slice[0..2]),
1668 (2, b"0b" | b"0B") | (8, b"0o" | b"0O") | (10, b"0d" | b"0D") | (16, b"0x" | b"0X")
1669 )
1670 {
1671 slice = &slice[2..];
1672 };
1673
1674 u32::try_from(x).unwrap()
1676 }
1677 };
1678
1679 if matches!(slice.first(), Some(&b'_' | &b'+' | &b'-')) {
1687 return Ok(interp.convert(0));
1688 }
1689
1690 if let Some(double_underscore) = slice.find("__") {
1692 slice = &slice[..double_underscore];
1693 }
1694
1695 let mut slice = std::borrow::Cow::from(slice);
1697 if slice.find("_").is_some() {
1698 slice.to_mut().retain(|&c| c != b'_');
1699 }
1700 loop {
1701 use std::num::IntErrorKind;
1702 let parsed = str::from_utf8(&slice)
1704 .map_err(|_| IntErrorKind::InvalidDigit)
1705 .and_then(|s| i64::from_str_radix(s, base).map_err(|err| err.kind().clone()));
1706 match parsed {
1707 Ok(int) => return Ok(interp.convert(sign * int)),
1708 Err(IntErrorKind::Empty | IntErrorKind::Zero) => return Ok(interp.convert(0)),
1709 Err(IntErrorKind::PosOverflow | IntErrorKind::NegOverflow) => {
1710 return Err(NotImplementedError::new().into());
1711 }
1712 _ => {
1713 match slice {
1715 std::borrow::Cow::Owned(ref mut data) => {
1716 data.pop();
1717 }
1718 std::borrow::Cow::Borrowed(data) => {
1719 slice = std::borrow::Cow::from(&data[..(data.len() - 1)]);
1720 }
1721 }
1722 }
1723 }
1724 }
1725}
1726
1727pub fn to_s(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1728 let _s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1729 Ok(value)
1730}
1731
1732pub fn upcase(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1733 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1734 let mut dup = s.clone();
1735 dup.make_uppercase();
1736 super::String::alloc_value(dup, interp)
1737}
1738
1739pub fn upcase_bang(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1740 check_frozen(interp, value)?;
1741
1742 let mut s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1743 let (effect, returned) = unsafe {
1746 let string_mut = s.as_inner_mut();
1747 let effect = string_mut.make_uppercase();
1750
1751 let s = s.take();
1752 (effect, super::String::box_into_value(s, value, interp)?)
1753 };
1754 if effect.changed() {
1755 Ok(returned)
1756 } else {
1757 Ok(Value::nil())
1758 }
1759}
1760
1761pub fn is_valid_encoding(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
1762 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1763 Ok(interp.convert(s.is_valid_encoding()))
1764}
1765
1766fn check_frozen(interp: &mut Artichoke, mut value: Value) -> Result<(), Error> {
1767 if !value.is_frozen(interp) {
1768 return Ok(());
1769 }
1770 let s = unsafe { super::String::unbox_from_value(&mut value, interp)? };
1771 let message = "can't modify frozen String: "
1772 .chars()
1773 .chain(s.inspect())
1774 .collect::<super::String>();
1775 Err(FrozenError::from(message.into_vec()).into())
1776}
1777
1778#[cfg(test)]
1779mod tests {
1780 use bstr::ByteSlice;
1781
1782 use super::*;
1783 use crate::test::prelude::*;
1784
1785 #[test]
1786 fn mutating_methods_may_raise_frozen_error() {
1787 #[track_caller]
1788 fn assert_is_frozen_err(result: Result<Value, Error>) {
1789 let err = result.unwrap_err();
1790 let message = err.message();
1791 let class_name = err.name();
1792 assert_eq!(class_name, "FrozenError");
1793 assert_eq!(message.as_bstr(), br#"can't modify frozen String: "foo""#.as_bstr());
1794 }
1795
1796 let mut interp = interpreter();
1797 let mut slf = interp.eval(b"'foo'").unwrap();
1798
1799 slf.freeze(&mut interp).unwrap();
1800
1801 let other = interp.try_convert_mut("bar").unwrap();
1802
1803 assert_is_frozen_err(append(&mut interp, slf, other));
1804 assert_is_frozen_err(aset(&mut interp, slf));
1805 assert_is_frozen_err(capitalize_bang(&mut interp, slf));
1806 assert_is_frozen_err(chomp_bang(&mut interp, slf, None));
1807 assert_is_frozen_err(chop_bang(&mut interp, slf));
1808 assert_is_frozen_err(clear(&mut interp, slf));
1809 assert_is_frozen_err(downcase_bang(&mut interp, slf));
1810 assert_is_frozen_err(initialize_copy(&mut interp, slf, other));
1811 assert_is_frozen_err(reverse_bang(&mut interp, slf));
1812 assert_is_frozen_err(swapcase_bang(&mut interp, slf));
1813 assert_is_frozen_err(upcase_bang(&mut interp, slf));
1814
1815 let index = interp.convert(0);
1816 let byte = interp.convert(0);
1817 assert_is_frozen_err(setbyte(&mut interp, slf, index, byte));
1818 }
1819}