From c670b5fb2c3caf70f7ae827b40e984121ae75c10 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:13:46 +0000 Subject: [PATCH] Refactor text stdlib to use generic helpers This commit introduces `unary_text_op` and `binary_text_predicate` to `src/stdlib/helpers.rs` and refactors `src/stdlib/text.rs` to use these helpers. This reduces code duplication for common text operations like `touppercase`, `tolowercase`, `trim`, `capitalize`, `reverse_text`, `starts_with`, and `ends_with`. The `trim` function now allocates an intermediate `String` to fit the generic helper signature, prioritizing maintainability and consistency over micro-optimization in this context. Verified with `cargo fmt`, `cargo clippy`, and `cargo test`. Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/helpers.rs | 43 ++++++++++++++++++++++++++++++ src/stdlib/text.rs | 62 +++++++++++++------------------------------ 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/stdlib/helpers.rs b/src/stdlib/helpers.rs index 5dd68395..adf0384f 100644 --- a/src/stdlib/helpers.rs +++ b/src/stdlib/helpers.rs @@ -473,3 +473,46 @@ pub fn expect_datetime(value: &Value) -> Result, Runti )), } } + +/// Helper for unary text operations (Text -> Text) +/// +/// Handles argument count checking, type extraction, operation execution, +/// and result wrapping. +/// +/// # Arguments +/// +/// * `func_name` - Name of the function for error messages +/// * `args` - Arguments passed to the function +/// * `op` - The text operation to perform +pub fn unary_text_op(func_name: &str, args: Vec, op: F) -> Result +where + F: Fn(&str) -> String, +{ + check_arg_count(func_name, &args, 1)?; + let text = expect_text(&args[0])?; + Ok(Value::Text(Arc::from(op(&text)))) +} + +/// Helper for binary text predicates ((Text, Text) -> Bool) +/// +/// Handles argument count checking, type extraction, operation execution, +/// and result wrapping. +/// +/// # Arguments +/// +/// * `func_name` - Name of the function for error messages +/// * `args` - Arguments passed to the function +/// * `op` - The predicate to perform +pub fn binary_text_predicate( + func_name: &str, + args: Vec, + op: F, +) -> Result +where + F: Fn(&str, &str) -> bool, +{ + check_arg_count(func_name, &args, 2)?; + let a = expect_text(&args[0])?; + let b = expect_text(&args[1])?; + Ok(Value::Bool(op(&a, &b))) +} diff --git a/src/stdlib/text.rs b/src/stdlib/text.rs index b91c428b..9ee46bda 100644 --- a/src/stdlib/text.rs +++ b/src/stdlib/text.rs @@ -1,4 +1,6 @@ -use super::helpers::{check_arg_count, expect_number, expect_text}; +use super::helpers::{ + binary_text_predicate, check_arg_count, expect_number, expect_text, unary_text_op, +}; use crate::interpreter::environment::Environment; use crate::interpreter::error::RuntimeError; use crate::interpreter::value::Value; @@ -74,19 +76,11 @@ fn parse_key_value_pairs(input: &str, delimiter: char) -> std::collections::Hash // which handles both text and lists pub fn native_touppercase(args: Vec) -> Result { - check_arg_count("touppercase", &args, 1)?; - - let text = expect_text(&args[0])?; - let uppercase = text.to_uppercase(); - Ok(Value::Text(Arc::from(uppercase))) + unary_text_op("touppercase", args, |s| s.to_uppercase()) } pub fn native_tolowercase(args: Vec) -> Result { - check_arg_count("tolowercase", &args, 1)?; - - let text = expect_text(&args[0])?; - let lowercase = text.to_lowercase(); - Ok(Value::Text(Arc::from(lowercase))) + unary_text_op("tolowercase", args, |s| s.to_lowercase()) } pub fn native_substring(args: Vec) -> Result { @@ -135,29 +129,15 @@ pub fn native_string_split(args: Vec) -> Result { } pub fn native_trim(args: Vec) -> Result { - check_arg_count("trim", &args, 1)?; - - let text = expect_text(&args[0])?; - let trimmed = text.trim(); - Ok(Value::Text(Arc::from(trimmed))) + unary_text_op("trim", args, |s| s.trim().to_string()) } pub fn native_starts_with(args: Vec) -> Result { - check_arg_count("starts_with", &args, 2)?; - - let text = expect_text(&args[0])?; - let prefix = expect_text(&args[1])?; - let result = text.starts_with(prefix.as_ref()); - Ok(Value::Bool(result)) + binary_text_predicate("starts_with", args, |s, p| s.starts_with(p)) } pub fn native_ends_with(args: Vec) -> Result { - check_arg_count("ends_with", &args, 2)?; - - let text = expect_text(&args[0])?; - let suffix = expect_text(&args[1])?; - let result = text.ends_with(suffix.as_ref()); - Ok(Value::Bool(result)) + binary_text_predicate("ends_with", args, |s, p| s.ends_with(p)) } /// Parse query string into WFL object @@ -278,26 +258,20 @@ pub fn native_padright(args: Vec) -> Result { } pub fn native_capitalize(args: Vec) -> Result { - check_arg_count("capitalize", &args, 1)?; - - let text = expect_text(&args[0])?; - let mut chars = text.chars(); - let result = match chars.next() { - Some(c) => { - let upper: String = c.to_uppercase().collect(); - format!("{}{}", upper, chars.as_str()) + unary_text_op("capitalize", args, |text| { + let mut chars = text.chars(); + match chars.next() { + Some(c) => { + let upper: String = c.to_uppercase().collect(); + format!("{}{}", upper, chars.as_str()) + } + None => String::new(), } - None => String::new(), - }; - Ok(Value::Text(Arc::from(result))) + }) } pub fn native_reverse_text(args: Vec) -> Result { - check_arg_count("reverse", &args, 1)?; - - let text = expect_text(&args[0])?; - let reversed: String = text.chars().rev().collect(); - Ok(Value::Text(Arc::from(reversed))) + unary_text_op("reverse", args, |text| text.chars().rev().collect()) } pub fn register_text(env: &mut Environment) {