diff --git a/core/functions/printf.rs b/core/functions/printf.rs index 8ffd446c0..00c504a6e 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. flags: - + 0 ! , #[inline(always)] pub fn exec_printf(values: &[Register]) -> crate::Result { if values.is_empty() { @@ -40,6 +72,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())); @@ -63,6 +109,119 @@ 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; + } + 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; + } + 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; + } + 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(), @@ -159,6 +318,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![ @@ -194,6 +376,178 @@ 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![ + // 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_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_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![ diff --git a/core/types.rs b/core/types.rs index cc9c78021..ce29d89ba 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)) }