Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/stdlib/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,68 @@ pub fn expect_number(value: &Value) -> Result<f64, RuntimeError> {
}
}

/// Helper for unary text operations (Text -> Text)
pub fn unary_text_op<F>(func_name: &str, args: Vec<Value>, op: F) -> Result<Value, RuntimeError>
where
F: Fn(&str) -> Arc<str>,
{
check_arg_count(func_name, &args, 1)?;
let text = expect_text(&args[0])?;
Ok(Value::Text(op(&text)))
}

/// Helper for binary text predicates ((Text, Text) -> Bool)
pub fn binary_text_predicate<F>(
func_name: &str,
args: Vec<Value>,
op: F,
) -> Result<Value, RuntimeError>
where
F: Fn(&str, &str) -> bool,
{
check_arg_count(func_name, &args, 2)?;
let text = expect_text(&args[0])?;
let other = expect_text(&args[1])?;
Ok(Value::Bool(op(&text, &other)))
}

/// Helper for unary list actions that modify the list in place and return Null
pub fn unary_list_action<F>(func_name: &str, args: Vec<Value>, op: F) -> Result<Value, RuntimeError>
where
F: Fn(&mut Vec<Value>),
{
check_arg_count(func_name, &args, 1)?;
let list = expect_list(&args[0])?;
op(&mut list.borrow_mut());
Ok(Value::Null)
}

/// Helper for binary list actions that modify the list in place and return Null
pub fn binary_list_action<F>(
func_name: &str,
args: Vec<Value>,
op: F,
) -> Result<Value, RuntimeError>
where
F: Fn(&mut Vec<Value>, Value),
{
check_arg_count(func_name, &args, 2)?;
let list = expect_list(&args[0])?;
let item = args[1].clone();
op(&mut list.borrow_mut(), item);
Ok(Value::Null)
}

/// Helper for unary list operations that return a value
pub fn unary_list_op<F>(func_name: &str, args: Vec<Value>, op: F) -> Result<Value, RuntimeError>
where
F: Fn(&mut Vec<Value>) -> Result<Value, RuntimeError>,
{
check_arg_count(func_name, &args, 1)?;
let list = expect_list(&args[0])?;
op(&mut list.borrow_mut())
}

/// Helper for unary math operations (f64 -> f64)
///
/// Handles argument count checking, type extraction, operation execution,
Expand Down
93 changes: 34 additions & 59 deletions src/stdlib/list.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::helpers::{check_arg_count, expect_list, expect_number, expect_text};
use super::helpers::{
binary_list_action, check_arg_count, expect_list, expect_number, expect_text,
unary_list_action, unary_list_op,
};
use crate::interpreter::environment::Environment;
use crate::interpreter::error::RuntimeError;
use crate::interpreter::value::Value;
Expand All @@ -21,30 +24,20 @@ pub fn native_length(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_push(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("push", &args, 2)?;

let list = expect_list(&args[0])?;
let item = args[1].clone();

list.borrow_mut().push(item);
Ok(Value::Null)
binary_list_action("push", args, |list, item| list.push(item))
}

pub fn native_pop(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("pop", &args, 1)?;

let list = expect_list(&args[0])?;
let mut list_ref = list.borrow_mut();

if list_ref.is_empty() {
return Err(RuntimeError::new(
"Cannot pop from an empty list".to_string(),
0,
0,
));
}

Ok(list_ref.pop().unwrap())
unary_list_op("pop", args, |list| {
if list.is_empty() {
return Err(RuntimeError::new(
"Cannot pop from an empty list".to_string(),
0,
0,
));
}
Ok(list.pop().unwrap())
})
}

pub fn native_contains(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -131,25 +124,20 @@ pub fn native_indexof(args: Vec<Value>) -> Result<Value, RuntimeError> {
// --- Batch 3: Basic List Manipulation ---

pub fn native_shift(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("shift", &args, 1)?;
let list = expect_list(&args[0])?;
let mut list_ref = list.borrow_mut();
if list_ref.is_empty() {
return Err(RuntimeError::new(
"Cannot shift from an empty list".to_string(),
0,
0,
));
}
Ok(list_ref.remove(0))
unary_list_op("shift", args, |list| {
if list.is_empty() {
return Err(RuntimeError::new(
"Cannot shift from an empty list".to_string(),
0,
0,
));
}
Ok(list.remove(0))
})
}

pub fn native_unshift(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("unshift", &args, 2)?;
let list = expect_list(&args[0])?;
let item = args[1].clone();
list.borrow_mut().insert(0, item);
Ok(Value::Null)
binary_list_action("unshift", args, |list, item| list.insert(0, item))
}

pub fn native_remove_at(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -193,10 +181,7 @@ pub fn native_insert_at(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_clear(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("clear", &args, 1)?;
let list = expect_list(&args[0])?;
list.borrow_mut().clear();
Ok(Value::Null)
unary_list_action("clear", args, |list| list.clear())
}

pub fn native_slice(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -262,14 +247,11 @@ pub fn native_count(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_fill(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("fill", &args, 2)?;
let list = expect_list(&args[0])?;
let value = args[1].clone();
let mut list_ref = list.borrow_mut();
for item in list_ref.iter_mut() {
*item = value.clone();
}
Ok(Value::Null)
binary_list_action("fill", args, |list, value| {
for item in list.iter_mut() {
*item = value.clone();
}
})
}

// --- Batch 5: Sort & Reverse ---
Expand Down Expand Up @@ -311,18 +293,11 @@ fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
}

pub fn native_sort(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("sort", &args, 1)?;
let list = expect_list(&args[0])?;
let mut list_ref = list.borrow_mut();
list_ref.sort_by(compare_values);
Ok(Value::Null)
unary_list_action("sort", args, |list| list.sort_by(compare_values))
}

pub fn native_reverse_list(args: Vec<Value>) -> Result<Value, RuntimeError> {
check_arg_count("reverse_list", &args, 1)?;
let list = expect_list(&args[0])?;
list.borrow_mut().reverse();
Ok(Value::Null)
unary_list_action("reverse_list", args, |list| list.reverse())
}

// --- Batch 6: List Search ---
Expand Down
64 changes: 20 additions & 44 deletions src/stdlib/text.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Value>) -> Result<Value, RuntimeError> {
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, |text| Arc::from(text.to_uppercase()))
}

pub fn native_tolowercase(args: Vec<Value>) -> Result<Value, RuntimeError> {
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, |text| Arc::from(text.to_lowercase()))
}

pub fn native_substring(args: Vec<Value>) -> Result<Value, RuntimeError> {
Expand Down Expand Up @@ -135,29 +129,15 @@ pub fn native_string_split(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_trim(args: Vec<Value>) -> Result<Value, RuntimeError> {
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, |text| Arc::from(text.trim()))
}

pub fn native_starts_with(args: Vec<Value>) -> Result<Value, RuntimeError> {
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, |text, prefix| text.starts_with(prefix))
}

pub fn native_ends_with(args: Vec<Value>) -> Result<Value, RuntimeError> {
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, |text, suffix| text.ends_with(suffix))
}

/// Parse query string into WFL object
Expand Down Expand Up @@ -278,26 +258,22 @@ pub fn native_padright(args: Vec<Value>) -> Result<Value, RuntimeError> {
}

pub fn native_capitalize(args: Vec<Value>) -> Result<Value, RuntimeError> {
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();
Arc::from(format!("{}{}", upper, chars.as_str()))
}
None => Arc::from(""),
}
None => String::new(),
};
Ok(Value::Text(Arc::from(result)))
})
}

pub fn native_reverse_text(args: Vec<Value>) -> Result<Value, RuntimeError> {
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| {
Arc::from(text.chars().rev().collect::<String>())
})
}

pub fn register_text(env: &mut Environment) {
Expand Down
Loading