diff --git a/src/format.rs b/src/format.rs index de5cbfe..10b82f1 100644 --- a/src/format.rs +++ b/src/format.rs @@ -474,6 +474,25 @@ impl Printf for &str { if spec.conversion_type == ConversionType::String { let mut s = String::new(); + // Take care of precision, putting the truncated string in `content` + let precision: usize = match spec.precision { + NumericParam::Literal(p) => p, + _ => { + return Err(PrintfError::Unknown); // should not happen at this point!! + } + } + .try_into() + .unwrap_or_default(); + let content_len = { + let mut content_len = precision.min(self.len()); + while !self.is_char_boundary(content_len) { + content_len -= 1; + } + content_len + }; + let content = &self[..content_len]; + + // Pad to width if needed, putting the padded string in `s` let width: usize = match spec.width { NumericParam::Literal(w) => w, _ => { @@ -482,18 +501,18 @@ impl Printf for &str { } .try_into() .unwrap_or_default(); - if spec.left_adj { - s.push_str(self); + s.push_str(content); while s.len() < width { s.push(' '); } } else { - while s.len() + self.len() < width { + while s.len() + content.len() < width { s.push(' '); } - s.push_str(self); + s.push_str(content); } + Ok(s) } else { Err(PrintfError::WrongType) diff --git a/src/parser.rs b/src/parser.rs index 62f5ac2..b69d66b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -131,7 +131,7 @@ fn take_conversion_specifier(s: &str) -> Result<(ConversionSpecifier, &str)> { space_sign: false, force_sign: false, width: NumericParam::Literal(0), - precision: NumericParam::Literal(6), + precision: NumericParam::FromArgument, // Placeholder - must not be returned! // ignore length modifier conversion_type: ConversionType::DecInt, }; @@ -203,6 +203,18 @@ fn take_conversion_specifier(s: &str) -> Result<(ConversionSpecifier, &str)> { } }; + if spec.precision == NumericParam::FromArgument { + // If precision is not specified, set to default value + let p = if spec.conversion_type == ConversionType::String { + // Default to max limit (aka no limit) for strings + i32::MAX + } else { + // Default to 6 for all other types + 6 + }; + spec.precision = NumericParam::Literal(p); + } + Ok((spec, &s[1..])) } diff --git a/tests/compare_to_libc.rs b/tests/compare_to_libc.rs index a76c8fd..f40cda0 100644 --- a/tests/compare_to_libc.rs +++ b/tests/compare_to_libc.rs @@ -112,6 +112,10 @@ fn test_float() { #[test] fn test_str() { check_fmt_s("test %% with string: %s yay\n", "FOO"); + check_fmt_s( + "%s", + "testing with a slightly longer string to make sure it doesn't truncate", + ); check_fmt("test char %c", '~'); let c_string = CString::new("test").unwrap(); check_fmt("%s", c_string.as_c_str()); @@ -120,6 +124,20 @@ fn test_str() { check_fmt_s("%4s", "𒀀"); // multi-byte character test (4 bytes) check_fmt_s("%-4sX", "A"); check_fmt_s("%-4sX", "𒀀"); // multi-byte character test (4 bytes) + check_fmt_s("%1.3s", "ABCDEFG"); + check_fmt_s("%1.4s", "𒀀𒀀"); // multi-byte character test (4 bytes per char) + check_fmt_s("%8.4s", "ABCDEFG"); + + // glibc does not handle UTF-8 strings correctly when truncating, but we cannot produce malformed UTF-8 + // strings in Rust. Instead, we round down to the nearest character boundary. + assert_eq!(sprintf!("%1.1s", "𒀀𒀀𒀀").unwrap(), " "); + assert_eq!(sprintf!("%1.2s", "𒀀𒀀𒀀").unwrap(), " "); + assert_eq!(sprintf!("%1.3s", "𒀀𒀀𒀀").unwrap(), " "); + assert_eq!(sprintf!("%1.4s", "𒀀𒀀𒀀").unwrap(), "𒀀"); + assert_eq!(sprintf!("%1.5s", "𒀀𒀀𒀀").unwrap(), "𒀀"); + assert_eq!(sprintf!("%1.6s", "𒀀𒀀𒀀").unwrap(), "𒀀"); + assert_eq!(sprintf!("%1.7s", "𒀀𒀀𒀀").unwrap(), "𒀀"); + assert_eq!(sprintf!("%1.8s", "𒀀𒀀𒀀").unwrap(), "𒀀𒀀"); } #[test]