pub fn dupstring(s: &str) -> String { s.to_string()
}
pub fn dupstring_wlen(s: &str, len: usize) -> String { let bytes = s.as_bytes();
let n = len.min(bytes.len());
String::from_utf8_lossy(&bytes[..n]).into_owned()
}
pub fn ztrdup(s: &str) -> String { s.to_string()
}
pub fn wcs_ztrdup(s: &str) -> String { s.to_string()
}
pub fn tricat(s1: &str, s2: &str, s3: &str) -> String { let mut result = String::with_capacity(s1.len() + s2.len() + s3.len());
result.push_str(s1);
result.push_str(s2);
result.push_str(s3);
result
}
pub fn zhtricat(s1: &str, s2: &str, s3: &str) -> String { tricat(s1, s2, s3)
}
pub fn dyncat(s1: &str, s2: &str) -> String { let mut result = String::with_capacity(s1.len() + s2.len());
result.push_str(s1);
result.push_str(s2);
result
}
pub fn bicat(s1: &str, s2: &str) -> String { let mut result = String::with_capacity(s1.len() + s2.len());
result.push_str(s1);
result.push_str(s2);
result
}
pub fn dupstrpfx(s: &str, len: usize) -> String { let bytes = s.as_bytes();
let n = len.min(bytes.len());
String::from_utf8_lossy(&bytes[..n]).into_owned()
}
pub fn ztrduppfx(s: &str, len: usize) -> String {
dupstrpfx(s, len)
}
pub fn appstr(base: &mut String, append: &str) {
base.push_str(append);
}
pub fn strend(str: &str) -> &str {
if str.is_empty() {
return str;
}
let bytes = str.as_bytes();
let mut i = bytes.len();
while i > 0 {
i -= 1;
if bytes[i] & 0xC0 != 0x80 {
return &str[i..];
}
}
str
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dupstring() {
assert_eq!(dupstring("hello"), "hello");
assert_eq!(dupstring(""), "");
}
#[test]
fn test_dupstring_wlen() {
assert_eq!(dupstring_wlen("hello world", 5), "hello");
assert_eq!(dupstring_wlen("hi", 50), "hi");
assert_eq!(dupstring_wlen("hello", 0), "");
}
#[test]
fn test_dupstring_wlen_byte_safe_at_codepoint_boundary() {
let s = "café";
let r = dupstring_wlen(s, 4);
assert!(r.starts_with("caf"));
}
#[test]
fn test_ztrdup() {
assert_eq!(ztrdup("permanent"), "permanent");
}
#[test]
fn test_wcs_ztrdup() {
assert_eq!(wcs_ztrdup("ünicode"), "ünicode");
}
#[test]
fn test_tricat() {
assert_eq!(tricat("a", "b", "c"), "abc");
assert_eq!(tricat("", "", ""), "");
assert_eq!(tricat("foo", "", "bar"), "foobar");
}
#[test]
fn test_zhtricat() {
assert_eq!(zhtricat("x", "y", "z"), "xyz");
}
#[test]
fn test_bicat() {
assert_eq!(bicat("hello", " world"), "hello world");
assert_eq!(bicat("", ""), "");
}
#[test]
fn test_dyncat() {
assert_eq!(dyncat("foo", "bar"), "foobar");
}
#[test]
fn test_appstr() {
let mut s = "hello".to_string();
appstr(&mut s, " world");
assert_eq!(s, "hello world");
}
#[test]
fn test_dupstrpfx() {
assert_eq!(dupstrpfx("hello world", 5), "hello");
assert_eq!(dupstrpfx("hi", 50), "hi");
assert_eq!(dupstrpfx("hi", 0), "");
}
#[test]
fn test_dupstrpfx_byte_safe() {
let _ = dupstrpfx("é", 1);
}
#[test]
fn test_ztrduppfx() {
assert_eq!(ztrduppfx("hello", 3), "hel");
}
#[test]
fn test_strend_returns_last_codepoint() {
assert_eq!(strend("hello"), "o");
assert_eq!(strend(""), "");
assert_eq!(strend("café"), "é");
assert_eq!(strend("a"), "a");
}
#[test]
fn tricat_concatenates_three_segments_in_order() {
assert_eq!(tricat("a", "b", "c"), "abc");
assert_eq!(tricat("", "b", "c"), "bc");
assert_eq!(tricat("a", "", "c"), "ac");
assert_eq!(tricat("a", "b", ""), "ab");
}
#[test]
fn dyncat_concatenates_two_segments() {
assert_eq!(dyncat("hello", " world"), "hello world");
assert_eq!(dyncat("", "x"), "x");
}
#[test]
fn ztrdup_returns_independent_owned_copy() {
let mut src = String::from("original");
let dup = ztrdup(&src);
src.clear();
assert_eq!(dup, "original",
"dup must survive source-side clear");
}
#[test]
fn dupstrpfx_handles_len_larger_than_input() {
assert_eq!(dupstrpfx("ab", 100), "ab");
assert_eq!(dupstrpfx("hello", 0), "");
assert_eq!(dupstrpfx("hello", 3), "hel");
}
#[test]
fn dyncat_empty_inputs_return_empty() {
assert_eq!(dyncat("", ""), "");
}
#[test]
fn bicat_concatenates_in_order_with_either_empty() {
assert_eq!(bicat("foo", "bar"), "foobar");
assert_eq!(bicat("", "bar"), "bar",
"c:152 — strcpy(ptr, \"\") writes only the NUL, ptr+0 starts s2");
assert_eq!(bicat("foo", ""), "foo",
"c:153 — strcpy(ptr+3, \"\") writes only the NUL");
assert_eq!(bicat("", ""), "");
}
#[test]
fn ztrduppfx_matches_dupstrpfx_byte_for_byte() {
for (s, len) in [("hello", 3usize), ("ab", 100), ("hello", 0), ("", 5)] {
assert_eq!(ztrduppfx(s, len), dupstrpfx(s, len),
"ztrduppfx/dupstrpfx divergence at ({:?}, {})", s, len);
}
}
#[test]
fn appstr_appends_in_place() {
let mut b = String::from("foo");
appstr(&mut b, "bar");
assert_eq!(b, "foobar");
appstr(&mut b, "");
assert_eq!(b, "foobar", "appending empty must leave base unchanged");
let mut e = String::new();
appstr(&mut e, "xyz");
assert_eq!(e, "xyz");
}
#[test]
fn strend_returns_only_last_character_for_multichar_input() {
assert_eq!(strend("hello"), "o");
assert_eq!(strend("ab"), "b");
assert_eq!(strend(""), "");
}
#[test]
fn dupstring_returns_owned_copy_with_identity_content() {
assert_eq!(dupstring("hello"), "hello");
assert_eq!(dupstring(""), "", "c:39 — empty input → len 0+1, strcpy copies NUL");
assert_eq!(dupstring("café"), "café");
assert_eq!(dupstring("字"), "字");
}
#[test]
fn dupstring_wlen_respects_byte_length_and_clamps_overflow() {
assert_eq!(dupstring_wlen("hello world", 5), "hello");
assert_eq!(dupstring_wlen("hello", 0), "");
assert_eq!(dupstring_wlen("ab", 100), "ab");
assert_eq!(dupstring_wlen("foo", 3), "foo");
}
#[test]
fn wcs_ztrdup_returns_independent_copy() {
let mut src = String::from("widechar");
let dup = wcs_ztrdup(&src);
src.clear();
assert_eq!(dup, "widechar",
"wide-char dup must survive source-side mutation");
assert_eq!(wcs_ztrdup("éàü字"), "éàü字");
}
#[test]
fn zhtricat_matches_tricat_byte_for_byte() {
for (a, b, c) in [
("foo", "bar", "baz"),
("", "x", ""),
("a", "", "z"),
("", "", ""),
] {
assert_eq!(zhtricat(a, b, c), tricat(a, b, c),
"lane divergence at ({:?}, {:?}, {:?})", a, b, c);
}
}
#[test]
fn ztrduppfx_clamps_oversize_len_safely() {
assert_eq!(ztrduppfx("hi", 100), "hi");
assert_eq!(ztrduppfx("", 5), "");
assert_eq!(ztrduppfx("abc", 2), "ab");
}
}