From fe9b9a20d05bb3445422e8f1e648ec1a5c5dcaee Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:58:07 +0000 Subject: [PATCH] Refactor stdlib pattern args logic to use helpers Co-authored-by: logbie <1138960+logbie@users.noreply.github.com> --- src/stdlib/helpers.rs | 9 ++ src/stdlib/pattern.rs | 186 +++++++------------------------------ src/stdlib/pattern_test.rs | 13 ++- 3 files changed, 52 insertions(+), 156 deletions(-) diff --git a/src/stdlib/helpers.rs b/src/stdlib/helpers.rs index 9dde4972..97e4e12b 100644 --- a/src/stdlib/helpers.rs +++ b/src/stdlib/helpers.rs @@ -639,3 +639,12 @@ where let list = expect_list(&args[0])?; op(list, val) } + +generate_expect!( + /// Extracts a compiled pattern from a WFL Value, returning it as a reference-counted CompiledPattern. + expect_pattern, + Pattern, + Rc, + "a pattern", + |p: &Rc| Rc::clone(p) +); diff --git a/src/stdlib/pattern.rs b/src/stdlib/pattern.rs index 4b3012c9..50230428 100644 --- a/src/stdlib/pattern.rs +++ b/src/stdlib/pattern.rs @@ -1,3 +1,4 @@ +use super::helpers::{check_arg_count, expect_pattern, expect_text}; use crate::interpreter::environment::Environment; use crate::interpreter::error::RuntimeError; use crate::interpreter::value::Value; @@ -19,74 +20,22 @@ pub fn register(env: &mut Environment) { /// Native function: pattern_matches(text, pattern) -> boolean /// Tests if text matches the given compiled pattern pub fn pattern_matches_native(args: Vec) -> Result { - if args.len() != 2 { - return Err(RuntimeError::new( - "pattern_matches requires exactly 2 arguments (text, pattern)".to_string(), - 0, - 0, - )); - } - - let text_str = match &args[0] { - Value::Text(s) => s.as_ref(), - _ => { - return Err(RuntimeError::new( - "First argument to pattern_matches must be text".to_string(), - 0, - 0, - )); - } - }; - - let compiled_pattern = match &args[1] { - Value::Pattern(p) => p, - _ => { - return Err(RuntimeError::new( - "Second argument to pattern_matches must be a compiled pattern".to_string(), - 0, - 0, - )); - } - }; + check_arg_count("pattern_matches", &args, 2)?; + let text_str = expect_text(&args[0])?; + let compiled_pattern = expect_pattern(&args[1])?; - let matches = compiled_pattern.matches(text_str); + let matches = compiled_pattern.matches(text_str.as_ref()); Ok(Value::Bool(matches)) } /// Native function: pattern_find(text, pattern) -> object or null /// Finds the first match of pattern in text pub fn pattern_find_native(args: Vec) -> Result { - if args.len() != 2 { - return Err(RuntimeError::new( - "pattern_find requires exactly 2 arguments (text, pattern)".to_string(), - 0, - 0, - )); - } - - let text_str = match &args[0] { - Value::Text(s) => s.as_ref(), - _ => { - return Err(RuntimeError::new( - "First argument to pattern_find must be text".to_string(), - 0, - 0, - )); - } - }; - - let compiled_pattern = match &args[1] { - Value::Pattern(p) => p, - _ => { - return Err(RuntimeError::new( - "Second argument to pattern_find must be a compiled pattern".to_string(), - 0, - 0, - )); - } - }; + check_arg_count("pattern_find", &args, 2)?; + let text_str = expect_text(&args[0])?; + let compiled_pattern = expect_pattern(&args[1])?; - match compiled_pattern.find(text_str) { + match compiled_pattern.find(text_str.as_ref()) { Some(match_result) => { let mut result_map = HashMap::new(); result_map.insert( @@ -120,37 +69,11 @@ pub fn pattern_find_native(args: Vec) -> Result { /// Native function: pattern_find_all(text, pattern) -> list /// Finds all matches of pattern in text pub fn pattern_find_all_native(args: Vec) -> Result { - if args.len() != 2 { - return Err(RuntimeError::new( - "pattern_find_all requires exactly 2 arguments (text, pattern)".to_string(), - 0, - 0, - )); - } - - let text_str = match &args[0] { - Value::Text(s) => s.as_ref(), - _ => { - return Err(RuntimeError::new( - "First argument to pattern_find_all must be text".to_string(), - 0, - 0, - )); - } - }; - - let compiled_pattern = match &args[1] { - Value::Pattern(p) => p, - _ => { - return Err(RuntimeError::new( - "Second argument to pattern_find_all must be a compiled pattern".to_string(), - 0, - 0, - )); - } - }; + check_arg_count("pattern_find_all", &args, 2)?; + let text_str = expect_text(&args[0])?; + let compiled_pattern = expect_pattern(&args[1])?; - let matches = compiled_pattern.find_all(text_str); + let matches = compiled_pattern.find_all(text_str.as_ref()); let mut result_list = Vec::new(); for match_result in matches { @@ -197,41 +120,14 @@ pub fn native_pattern_replace( )); } - let text = match &args[0] { - Value::Text(t) => t.as_ref(), - _ => { - return Err(RuntimeError::new( - "First argument must be text".to_string(), - line, - column, - )); - } - }; - - let _pattern = match &args[1] { - Value::Pattern(p) => p.as_ref(), - _ => { - return Err(RuntimeError::new( - "Second argument must be a pattern".to_string(), - line, - column, - )); - } - }; - - let _replacement = match &args[2] { - Value::Text(t) => t.as_ref(), - _ => { - return Err(RuntimeError::new( - "Third argument must be text".to_string(), - line, - column, - )); - } - }; + let text = expect_text(&args[0]).map_err(|e| RuntimeError::new(e.message, line, column))?; + let _pattern = + expect_pattern(&args[1]).map_err(|e| RuntimeError::new(e.message, line, column))?; + let _replacement = + expect_text(&args[2]).map_err(|e| RuntimeError::new(e.message, line, column))?; // TODO: Update to use new pattern system for replacement - Ok(Value::Text(Arc::from(text))) + Ok(Value::Text(Arc::clone(&text))) } /// Native function for pattern splitting (called by interpreter) @@ -248,41 +144,27 @@ pub fn native_pattern_split( )); } - let text = match &args[0] { - Value::Text(t) => t.as_ref(), - _ => { - return Err(RuntimeError::new( - "First argument must be text".to_string(), - line, - column, - )); - } - }; - - let pattern = match &args[1] { - Value::Pattern(p) => p, - _ => { - return Err(RuntimeError::new( - "Second argument must be a pattern".to_string(), - line, - column, - )); - } - }; + let text = expect_text(&args[0]).map_err(|e| RuntimeError::new(e.message, line, column))?; + let pattern = + expect_pattern(&args[1]).map_err(|e| RuntimeError::new(e.message, line, column))?; // Find all matches of the pattern in the text - let matches = pattern.find_all(text); + let matches = pattern.find_all(text.as_ref()); // If no matches, return the entire text as a single element if matches.is_empty() { - let parts = vec![Value::Text(Arc::from(text))]; + let parts = vec![Value::Text(Arc::clone(&text))]; return Ok(Value::List(Rc::new(RefCell::new(parts)))); } // Build character-to-byte index mapping - let char_to_byte: Vec = text.char_indices().map(|(byte_idx, _)| byte_idx).collect(); + let char_to_byte: Vec = text + .as_ref() + .char_indices() + .map(|(byte_idx, _)| byte_idx) + .collect(); let mut char_to_byte = char_to_byte; - char_to_byte.push(text.len()); // Add final byte position + char_to_byte.push(text.as_ref().len()); // Add final byte position // Split the text at match positions let mut parts = Vec::new(); @@ -293,19 +175,19 @@ pub fn native_pattern_split( let start_byte = if match_result.start < char_to_byte.len() { char_to_byte[match_result.start] } else { - text.len() + text.as_ref().len() }; let last_end_byte = if last_end_char < char_to_byte.len() { char_to_byte[last_end_char] } else { - text.len() + text.as_ref().len() }; // Add the text before this match if match_result.start > last_end_char || (match_result.start == last_end_char && last_end_char == 0) { - let part = &text[last_end_byte..start_byte]; + let part = &text.as_ref()[last_end_byte..start_byte]; parts.push(Value::Text(Arc::from(part))); } else if match_result.start == last_end_char && last_end_char > 0 { // Add empty string for consecutive matches @@ -317,7 +199,7 @@ pub fn native_pattern_split( // Add any remaining text after the last match if last_end_char < char_to_byte.len() { let last_end_byte = char_to_byte[last_end_char]; - let part = &text[last_end_byte..]; + let part = &text.as_ref()[last_end_byte..]; parts.push(Value::Text(Arc::from(part))); } diff --git a/src/stdlib/pattern_test.rs b/src/stdlib/pattern_test.rs index a7a4a9a2..bf0625b5 100644 --- a/src/stdlib/pattern_test.rs +++ b/src/stdlib/pattern_test.rs @@ -21,7 +21,7 @@ mod tests { result .unwrap_err() .to_string() - .contains("exactly 2 arguments") + .contains("expects 2 arguments") ); } @@ -33,7 +33,7 @@ mod tests { result .unwrap_err() .to_string() - .contains("exactly 2 arguments") + .contains("expects 2 arguments") ); } @@ -45,7 +45,7 @@ mod tests { result .unwrap_err() .to_string() - .contains("exactly 2 arguments") + .contains("expects 2 arguments") ); } @@ -57,6 +57,11 @@ mod tests { ]; let result = pattern_matches_native(args); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("First argument")); + assert!( + result + .unwrap_err() + .to_string() + .contains("Expected text, got Number") + ); } }