From 82a5a164786c069fd7c45e652c8b72ed1c324cdf Mon Sep 17 00:00:00 2001 From: luizgfc Date: Sun, 17 Aug 2025 16:15:14 -0300 Subject: [PATCH 1/5] core/printf: %u substitution type implementation --- core/functions/printf.rs | 37 +++++++++++++++++++++++++++++++++++++ core/types.rs | 7 +++++++ 2 files changed, 44 insertions(+) diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 8ffd446c0..316eebcf3 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -40,6 +40,20 @@ pub fn exec_printf(values: &[Register]) -> crate::Result { } args_index += 1; } + Some('u') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Integer(_) => { + let converted_value = value.as_uint(); + result.push_str(&format!("{converted_value}")) + } + _ => result.push('0'), + } + args_index += 1; + } Some('s') => { if args_index >= values.len() { return Err(LimboError::InvalidArgument("not enough arguments".into())); @@ -159,6 +173,29 @@ mod tests { } } + #[test] + fn test_printf_unsigned_integer_formatting() { + let test_cases = vec![ + // Basic + (vec![text("Number: %u"), integer(42)], text("Number: 42")), + // Multiple numbers + ( + vec![text("%u + %u = %u"), integer(2), integer(3), integer(5)], + text("2 + 3 = 5"), + ), + // Negative number should be represented as its uint representation + ( + vec![text("Negative: %u"), integer(-1)], + text("Negative: 18446744073709551615"), + ), + // Non-numeric value defaults to 0 + (vec![text("NaN: %u"), text("not a number")], text("NaN: 0")), + ]; + for (input, output) in test_cases { + assert_eq!(exec_printf(&input).unwrap(), *output.get_value()) + } + } + #[test] fn test_printf_float_formatting() { let test_cases = vec![ diff --git a/core/types.rs b/core/types.rs index 67fdbfdb8..5d7d94ca4 100644 --- a/core/types.rs +++ b/core/types.rs @@ -350,6 +350,13 @@ impl Value { } } + pub fn as_uint(&self) -> u64 { + match self { + Value::Integer(i) => (*i).cast_unsigned(), + _ => 0, + } + } + pub fn from_text(text: &str) -> Self { Value::Text(Text::new(text)) } From 078b0aca795de9c40ca2ca285e14bc7e23a54d49 Mon Sep 17 00:00:00 2001 From: luizgfc Date: Tue, 26 Aug 2025 21:20:10 -0300 Subject: [PATCH 2/5] core/printf: %e and %E substitution types implementation --- core/functions/printf.rs | 172 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 316eebcf3..7033dcf62 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -1,8 +1,40 @@ +use core::f64; + use crate::types::Value; use crate::vdbe::Register; use crate::LimboError; -// TODO: Support %!.3s %i, %x, %X, %o, %e, %E, %c. flags: - + 0 ! , +fn get_exponential_formatted_str(number: &f64, uppercase: bool) -> crate::Result { + let pre_formatted = format!("{number:.6e}"); + let mut parts = pre_formatted.split("e"); + + let maybe_base = parts.next(); + let maybe_exponent = parts.next(); + + let mut result = String::new(); + match (maybe_base, maybe_exponent) { + (Some(base), Some(exponent)) => { + result.push_str(base); + result.push_str(if uppercase { "E" } else { "e" }); + + match exponent.parse::() { + Ok(exponent_number) => { + let exponent_fmt = format!("{exponent_number:+03}"); + result.push_str(&exponent_fmt); + Ok(result) + } + Err(_) => Err(LimboError::InternalError( + "unable to parse exponential expression's exponent".into(), + )), + } + } + (_, _) => Err(LimboError::InternalError( + "unable to parse exponential expression".into(), + )), + } +} + +// TODO: Support %!.3s %x, %X, %o, %c. flags: - + 0 ! , #[inline(always)] pub fn exec_printf(values: &[Register]) -> crate::Result { if values.is_empty() { @@ -77,6 +109,72 @@ pub fn exec_printf(values: &[Register]) -> crate::Result { } args_index += 1; } + Some('e') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Float(f) => match get_exponential_formatted_str(f, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + }, + Value::Integer(i) => { + let f = *i as f64; + match get_exponential_formatted_str(&f, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + } + } + Value::Text(s) => { + let number: f64 = s + .as_str() + .trim_start() + .trim_end_matches(|c: char| !c.is_numeric()) + .parse() + .unwrap_or(0.0); + match get_exponential_formatted_str(&number, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + }; + } + _ => result.push_str("0.000000e+00"), + } + args_index += 1; + } + Some('E') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Float(f) => match get_exponential_formatted_str(f, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + }, + Value::Integer(i) => { + let f = *i as f64; + match get_exponential_formatted_str(&f, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + } + } + Value::Text(s) => { + let number: f64 = s + .as_str() + .trim_start() + .trim_end_matches(|c: char| !c.is_numeric()) + .parse() + .unwrap_or(0.0); + match get_exponential_formatted_str(&number, false) { + Ok(str) => result.push_str(&str), + Err(e) => return Err(e), + }; + } + _ => result.push_str("0.000000e+00"), + } + args_index += 1; + } None => { return Err(LimboError::InvalidArgument( "incomplete format specifier".into(), @@ -231,6 +329,78 @@ mod tests { } } + #[test] + fn test_printf_exponential_formatting() { + let test_cases = vec![ + // Simple number + ( + vec![text("Exp: %e"), float(23000000.0)], + text("Exp: 2.300000e+07"), + ), + // Negative number + ( + vec![text("Exp: %e"), float(-23000000.0)], + text("Exp: -2.300000e+07"), + ), + // Non integer float + ( + vec![text("Exp: %e"), float(250.375)], + text("Exp: 2.503750e+02"), + ), + // Positive, but smaller than zero + ( + vec![text("Exp: %e"), float(0.0003235)], + text("Exp: 3.235000e-04"), + ), + // Zero + (vec![text("Exp: %e"), float(0.0)], text("Exp: 0.000000e+00")), + // Uppercase "e" + ( + vec![text("Exp: %e"), float(0.0003235)], + text("Exp: 3.235000e-04"), + ), + // String with integer number + ( + vec![text("Exp: %e"), text("123")], + text("Exp: 1.230000e+02"), + ), + // String with floating point number + ( + vec![text("Exp: %e"), text("123.45")], + text("Exp: 1.234500e+02"), + ), + // String with number with leftmost zeroes + ( + vec![text("Exp: %e"), text("00123")], + text("Exp: 1.230000e+02"), + ), + // String with text + ( + vec![text("Exp: %e"), text("test")], + text("Exp: 0.000000e+00"), + ), + // String starting with number, but with text on the end + ( + vec![text("Exp: %e"), text("123ab")], + text("Exp: 1.230000e+02"), + ), + // String starting with text, but with number on the end + ( + vec![text("Exp: %e"), text("ab123")], + text("Exp: 0.000000e+00"), + ), + // String with exponential representation + ( + vec![text("Exp: %e"), text("1.230000e+02")], + text("Exp: 1.230000e+02"), + ), + ]; + + for (input, expected) in test_cases { + assert_eq!(exec_printf(&input).unwrap(), *expected.get_value()); + } + } + #[test] fn test_printf_mixed_formatting() { let test_cases = vec![ From 3f7a7d0e39b9f39fdf325fbc7fcb1c0d46e4aa9f Mon Sep 17 00:00:00 2001 From: luizgfc Date: Thu, 28 Aug 2025 15:24:33 -0300 Subject: [PATCH 3/5] core/printf: %c substitution type implementation --- core/functions/printf.rs | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 7033dcf62..3b1cc59f8 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -34,7 +34,7 @@ fn get_exponential_formatted_str(number: &f64, uppercase: bool) -> crate::Result } } -// TODO: Support %!.3s %x, %X, %o, %c. flags: - + 0 ! , +// TODO: Support %!.3s %x, %X, %o. flags: - + 0 ! , #[inline(always)] pub fn exec_printf(values: &[Register]) -> crate::Result { if values.is_empty() { @@ -175,6 +175,17 @@ pub fn exec_printf(values: &[Register]) -> crate::Result { } args_index += 1; } + Some('c') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + let value_str: String = format!("{value}"); + if !value_str.is_empty() { + result.push_str(&value_str[0..1]); + } + args_index += 1; + } None => { return Err(LimboError::InvalidArgument( "incomplete format specifier".into(), @@ -329,6 +340,35 @@ mod tests { } } + #[test] + fn test_printf_character_formatting() { + let test_cases = vec![ + // Simple character + (vec![text("character: %c"), text("a")], text("character: a")), + // Character with string + ( + vec![text("character: %c"), text("this is a test")], + text("character: t"), + ), + // Character with empty + (vec![text("character: %c"), text("")], text("character: ")), + // Character with integer + ( + vec![text("character: %c"), integer(123)], + text("character: 1"), + ), + // Character with float + ( + vec![text("character: %c"), float(42.5)], + text("character: 4"), + ), + ]; + + for (input, expected) in test_cases { + assert_eq!(exec_printf(&input).unwrap(), *expected.get_value()); + } + } + #[test] fn test_printf_exponential_formatting() { let test_cases = vec![ From 38d528537a9f0aff151711511181288b329c01e4 Mon Sep 17 00:00:00 2001 From: luizgfc Date: Fri, 29 Aug 2025 17:56:48 -0300 Subject: [PATCH 4/5] core/printf: %x and %X substitution types implementation --- core/functions/printf.rs | 64 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 3b1cc59f8..030ca7192 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -34,7 +34,7 @@ fn get_exponential_formatted_str(number: &f64, uppercase: bool) -> crate::Result } } -// TODO: Support %!.3s %x, %X, %o. flags: - + 0 ! , +// TODO: Support %!.3s, %o. flags: - + 0 ! , #[inline(always)] pub fn exec_printf(values: &[Register]) -> crate::Result { if values.is_empty() { @@ -186,6 +186,30 @@ pub fn exec_printf(values: &[Register]) -> crate::Result { } args_index += 1; } + Some('x') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Float(f) => result.push_str(&format!("{:x}", *f as i64)), + Value::Integer(i) => result.push_str(&format!("{i:x}")), + _ => result.push('0'), + } + args_index += 1; + } + Some('X') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Float(f) => result.push_str(&format!("{:X}", *f as i64)), + Value::Integer(i) => result.push_str(&format!("{i:X}")), + _ => result.push('0'), + } + args_index += 1; + } None => { return Err(LimboError::InvalidArgument( "incomplete format specifier".into(), @@ -441,6 +465,44 @@ mod tests { } } + #[test] + fn test_printf_hexadecimal_formatting() { + let test_cases = vec![ + // Simple number + (vec![text("hex: %x"), integer(4)], text("hex: 4")), + // Bigger Number + ( + vec![text("hex: %x"), integer(15565303546)], + text("hex: 39fc3aefa"), + ), + // Uppercase letters + ( + vec![text("hex: %X"), integer(15565303546)], + text("hex: 39FC3AEFA"), + ), + // Negative + ( + vec![text("hex: %x"), integer(-15565303546)], + text("hex: fffffffc603c5106"), + ), + // Float + (vec![text("hex: %x"), float(42.5)], text("hex: 2a")), + // Negative Float + ( + vec![text("hex: %x"), float(-42.5)], + text("hex: ffffffffffffffd6"), + ), + // Text + (vec![text("hex: %x"), text("42")], text("hex: 0")), + // Empty Text + (vec![text("hex: %x"), text("")], text("hex: 0")), + ]; + + for (input, expected) in test_cases { + assert_eq!(exec_printf(&input).unwrap(), *expected.get_value()); + } + } + #[test] fn test_printf_mixed_formatting() { let test_cases = vec![ From 528cab55c1b5dc3dc3eb7477464598ab6bd7cc9b Mon Sep 17 00:00:00 2001 From: luizgfc Date: Fri, 29 Aug 2025 18:34:07 -0300 Subject: [PATCH 5/5] core/printf: %o substitution type implementation --- core/functions/printf.rs | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 030ca7192..00c504a6e 100644 --- a/core/functions/printf.rs +++ b/core/functions/printf.rs @@ -34,7 +34,7 @@ fn get_exponential_formatted_str(number: &f64, uppercase: bool) -> crate::Result } } -// TODO: Support %!.3s, %o. flags: - + 0 ! , +// TODO: Support %!.3s. flags: - + 0 ! , #[inline(always)] pub fn exec_printf(values: &[Register]) -> crate::Result { if values.is_empty() { @@ -210,6 +210,18 @@ pub fn exec_printf(values: &[Register]) -> crate::Result { } args_index += 1; } + Some('o') => { + if args_index >= values.len() { + return Err(LimboError::InvalidArgument("not enough arguments".into())); + } + let value = &values[args_index].get_value(); + match value { + Value::Float(f) => result.push_str(&format!("{:o}", *f as i64)), + Value::Integer(i) => result.push_str(&format!("{i:o}")), + _ => result.push('0'), + } + args_index += 1; + } None => { return Err(LimboError::InvalidArgument( "incomplete format specifier".into(), @@ -503,6 +515,39 @@ mod tests { } } + #[test] + fn test_printf_octal_formatting() { + let test_cases = vec![ + // Simple number + (vec![text("octal: %o"), integer(4)], text("octal: 4")), + // Bigger Number + ( + vec![text("octal: %o"), integer(15565303546)], + text("octal: 163760727372"), + ), + // Negative + ( + vec![text("octal: %o"), integer(-15565303546)], + text("octal: 1777777777614017050406"), + ), + // Float + (vec![text("octal: %o"), float(42.5)], text("octal: 52")), + // Negative Float + ( + vec![text("octal: %o"), float(-42.5)], + text("octal: 1777777777777777777726"), + ), + // Text + (vec![text("octal: %o"), text("42")], text("octal: 0")), + // Empty Text + (vec![text("octal: %o"), text("")], text("octal: 0")), + ]; + + for (input, expected) in test_cases { + assert_eq!(exec_printf(&input).unwrap(), *expected.get_value()); + } + } + #[test] fn test_printf_mixed_formatting() { let test_cases = vec![