use crate::frontend::ast::Expr;
use crate::runtime::validation::validate_arg_count;
use crate::runtime::{DataFrameColumn, InterpreterError, Value};
use std::sync::Arc;
pub fn eval_method_call<F>(
receiver: &Expr,
method: &str,
args: &[Expr],
mut eval_expr: F,
mut eval_dataframe_filter: impl FnMut(&Value, &[Expr]) -> Result<Value, InterpreterError>,
) -> Result<Value, InterpreterError>
where
F: FnMut(&Expr) -> Result<Value, InterpreterError>,
{
let receiver_value = eval_expr(receiver)?;
if matches!(receiver_value, Value::DataFrame { .. }) && method == "filter" {
return eval_dataframe_filter(&receiver_value, args);
}
let arg_values: Result<Vec<_>, _> = args.iter().map(eval_expr).collect();
let arg_values = arg_values?;
dispatch_method_call(&receiver_value, method, &arg_values, args.is_empty())
}
pub fn dispatch_method_call(
receiver: &Value,
method: &str,
arg_values: &[Value],
args_empty: bool,
) -> Result<Value, InterpreterError> {
match receiver {
Value::String(s) => eval_string_method(s, method, arg_values),
Value::Array(arr) => eval_array_method_simple(arr, method, arg_values),
Value::Float(f) => eval_float_method(*f, method, args_empty),
Value::Integer(n) => eval_integer_method(*n, method, arg_values),
Value::DataFrame { columns } => eval_dataframe_method_simple(columns, method, arg_values),
_ => eval_generic_method(receiver, method, args_empty),
}
}
pub fn eval_float_method(
f: f64,
method: &str,
args_empty: bool,
) -> Result<Value, InterpreterError> {
if method == "powf" {
return Err(InterpreterError::RuntimeError(
"Float method 'powf' not available. Use ** operator for exponentiation (e.g., 2.0 ** 3.0)".to_string(),
));
}
if !args_empty {
return Err(InterpreterError::RuntimeError(format!(
"Float method '{method}' takes no arguments"
)));
}
match method {
"sqrt" => Ok(Value::Float(f.sqrt())),
"abs" => Ok(Value::Float(f.abs())),
"round" => Ok(Value::Float(f.round())),
"floor" => Ok(Value::Float(f.floor())),
"ceil" => Ok(Value::Float(f.ceil())),
"to_int" | "to_integer" => Ok(Value::Integer(f as i64)),
"to_string" => Ok(Value::from_string(f.to_string())),
_ => Err(InterpreterError::RuntimeError(format!(
"Unknown float method: {method}"
))),
}
}
pub fn eval_integer_method(
n: i64,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
match method {
"abs" => {
validate_arg_count("Integer.abs", arg_values, 0)?;
Ok(Value::Integer(n.abs()))
}
"to_string" => {
validate_arg_count("Integer.to_string", arg_values, 0)?;
Ok(Value::from_string(n.to_string()))
}
"pow" => {
validate_arg_count("Integer.pow", arg_values, 1)?;
match &arg_values[0] {
Value::Integer(exp) => {
if *exp < 0 {
return Err(InterpreterError::RuntimeError(
"Integer pow() exponent must be non-negative".to_string(),
));
}
let result = n.pow(*exp as u32);
Ok(Value::Integer(result))
}
_ => Err(InterpreterError::TypeError(format!(
"Integer pow() requires integer exponent, got {}",
arg_values[0].type_name()
))),
}
}
_ => Err(InterpreterError::RuntimeError(format!(
"Unknown integer method: {method}"
))),
}
}
pub fn eval_generic_method(
receiver: &Value,
method: &str,
args_empty: bool,
) -> Result<Value, InterpreterError> {
if method == "to_string" && args_empty {
Ok(Value::from_string(receiver.to_string()))
} else {
Err(InterpreterError::RuntimeError(format!(
"Method '{}' not found for type {}",
method,
receiver.type_name()
)))
}
}
pub fn eval_string_method(
s: &Arc<str>,
method: &str,
args: &[Value],
) -> Result<Value, InterpreterError> {
super::eval_string_methods::eval_string_method(s, method, args)
}
pub fn eval_array_method_simple(
arr: &Arc<[Value]>,
method: &str,
args: &[Value],
) -> Result<Value, InterpreterError> {
match method {
"len" | "length" => {
validate_arg_count("Array.len", args, 0)?;
Ok(Value::Integer(arr.len() as i64))
}
"is_empty" => {
validate_arg_count("Array.is_empty", args, 0)?;
Ok(Value::Bool(arr.is_empty()))
}
"slice" => {
validate_arg_count("Array.slice", args, 2)?;
match (&args[0], &args[1]) {
(Value::Integer(start), Value::Integer(end)) => {
let start_idx = (*start).max(0) as usize;
let end_idx = (*end).max(0) as usize;
let slice: Vec<Value> = arr
.iter()
.skip(start_idx)
.take(end_idx.saturating_sub(start_idx))
.cloned()
.collect();
Ok(Value::from_array(slice))
}
_ => Err(InterpreterError::RuntimeError(
"Array.slice() expects two integer arguments".to_string(),
)),
}
}
"join" => {
validate_arg_count("Array.join", args, 1)?;
match &args[0] {
Value::String(separator) => {
let strings: Vec<String> = arr
.iter()
.map(|v| match v {
Value::String(s) => s.to_string(),
_ => format!("{v}"),
})
.collect();
Ok(Value::from_string(strings.join(separator.as_ref())))
}
_ => Err(InterpreterError::RuntimeError(
"Array.join() expects a string argument".to_string(),
)),
}
}
"unique" => {
validate_arg_count("Array.unique", args, 0)?;
let mut seen = std::collections::HashSet::new();
let unique: Vec<Value> = arr
.iter()
.filter(|v| {
let key = format!("{v:?}"); seen.insert(key)
})
.cloned()
.collect();
Ok(Value::from_array(unique))
}
_ => {
Err(InterpreterError::RuntimeError(format!(
"Array method '{method}' requires interpreter context"
)))
}
}
}
pub fn eval_dataframe_method_simple(
columns: &[DataFrameColumn],
method: &str,
args: &[Value],
) -> Result<Value, InterpreterError> {
match method {
"shape" => {
validate_arg_count("DataFrame.shape", args, 0)?;
let rows = columns.first().map_or(0, |c| c.values.len());
let cols = columns.len();
Ok(Value::Tuple(Arc::from(
vec![Value::Integer(rows as i64), Value::Integer(cols as i64)].as_slice(),
)))
}
"columns" => {
validate_arg_count("DataFrame.columns", args, 0)?;
let col_names = columns
.iter()
.map(|c| Value::from_string(c.name.clone()))
.collect();
Ok(Value::from_array(col_names))
}
_ => {
super::eval_dataframe_ops::eval_dataframe_method(columns, method, args)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eval_float_method() {
assert_eq!(
eval_float_method(4.0, "sqrt", true).expect("operation should succeed in test"),
Value::Float(2.0)
);
assert_eq!(
eval_float_method(-3.5, "abs", true).expect("operation should succeed in test"),
Value::Float(3.5)
);
assert_eq!(
eval_float_method(3.7, "round", true).expect("operation should succeed in test"),
Value::Float(4.0)
);
assert_eq!(
eval_float_method(3.7, "floor", true).expect("operation should succeed in test"),
Value::Float(3.0)
);
assert_eq!(
eval_float_method(3.2, "ceil", true).expect("operation should succeed in test"),
Value::Float(4.0)
);
}
#[test]
fn test_eval_integer_method() {
assert_eq!(
eval_integer_method(-5, "abs", &[]).expect("operation should succeed in test"),
Value::Integer(5)
);
let result =
eval_integer_method(42, "to_string", &[]).expect("operation should succeed in test");
match result {
Value::String(s) => assert_eq!(s.as_ref(), "42"),
_ => panic!("Expected string value"),
}
assert_eq!(
eval_integer_method(2, "pow", &[Value::Integer(3)])
.expect("operation should succeed in test"),
Value::Integer(8)
);
assert_eq!(
eval_integer_method(5, "pow", &[Value::Integer(2)])
.expect("operation should succeed in test"),
Value::Integer(25)
);
assert_eq!(
eval_integer_method(10, "pow", &[Value::Integer(0)])
.expect("operation should succeed in test"),
Value::Integer(1)
);
assert!(eval_integer_method(2, "pow", &[]).is_err()); assert!(eval_integer_method(2, "pow", &[Value::Integer(-1)]).is_err()); assert!(eval_integer_method(2, "pow", &[Value::String("3".into())]).is_err());
}
#[test]
fn test_eval_generic_method() {
let val = Value::Integer(42);
let result =
eval_generic_method(&val, "to_string", true).expect("operation should succeed in test");
match result {
Value::String(s) => assert_eq!(s.as_ref(), "42"),
_ => panic!("Expected string value"),
}
assert!(eval_generic_method(&val, "unknown", true).is_err());
}
#[test]
fn test_method_argument_validation() {
assert!(eval_float_method(4.0, "sqrt", false).is_err());
assert!(eval_integer_method(5, "abs", &[Value::Integer(1)]).is_err());
assert!(eval_generic_method(&Value::Nil, "type", false).is_err());
}
#[test]
fn test_eval_array_method_simple_match_arms() {
let arr = Arc::from(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]);
let result =
eval_array_method_simple(&arr, "len", &[]).expect("operation should succeed in test");
assert_eq!(result, Value::Integer(3), "len should return array length");
let result = eval_array_method_simple(&arr, "length", &[])
.expect("operation should succeed in test");
assert_eq!(
result,
Value::Integer(3),
"length should return array length"
);
let result = eval_array_method_simple(&arr, "is_empty", &[])
.expect("operation should succeed in test");
assert_eq!(
result,
Value::Bool(false),
"is_empty should return false for non-empty"
);
let empty_arr = Arc::from(vec![]);
let result = eval_array_method_simple(&empty_arr, "is_empty", &[])
.expect("operation should succeed in test");
assert_eq!(
result,
Value::Bool(true),
"is_empty should return true for empty"
);
}
#[test]
fn test_eval_array_method_simple_negation_operators() {
let arr = Arc::from(vec![Value::Integer(1)]);
let result = eval_array_method_simple(&arr, "len", &[Value::Integer(1)]);
assert!(result.is_err(), "len should reject arguments");
let result = eval_array_method_simple(&arr, "is_empty", &[Value::Integer(1)]);
assert!(result.is_err(), "is_empty should reject arguments");
}
#[test]
fn test_eval_dataframe_method_simple_match_arms() {
let columns = vec![
DataFrameColumn {
name: "col1".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
},
DataFrameColumn {
name: "col2".to_string(),
values: vec![Value::Integer(3), Value::Integer(4)],
},
];
let result = eval_dataframe_method_simple(&columns, "columns", &[])
.expect("operation should succeed in test");
match result {
Value::Array(arr) => {
assert_eq!(arr.len(), 2, "Should return 2 column names");
assert_eq!(arr[0], Value::from_string("col1".to_string()));
assert_eq!(arr[1], Value::from_string("col2".to_string()));
}
_ => panic!("Expected array of column names"),
}
}
#[test]
fn test_eval_dataframe_method_simple_negation_operator() {
let columns = vec![DataFrameColumn {
name: "col1".to_string(),
values: vec![Value::Integer(1)],
}];
let result = eval_dataframe_method_simple(&columns, "columns", &[Value::Integer(1)]);
assert!(result.is_err(), "columns method should reject arguments");
}
#[test]
fn test_dispatch_method_call_match_arms() {
let arr_val = Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)]));
let result = dispatch_method_call(&arr_val, "len", &[], true)
.expect("operation should succeed in test");
assert_eq!(result, Value::Integer(2), "Array should dispatch to len");
let df_val = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "test".to_string(),
values: vec![Value::Integer(1)],
}],
};
let result = dispatch_method_call(&df_val, "columns", &[], true)
.expect("operation should succeed in test");
match result {
Value::Array(_) => {} _ => panic!("DataFrame should dispatch to columns"),
}
}
}
#[cfg(test)]
mod mutation_tests {
use super::*;
#[test]
fn test_dispatch_method_call_float_match_arm() {
let float_val = Value::Float(4.0);
let result = dispatch_method_call(&float_val, "sqrt", &[], true);
assert!(result.is_ok(), "Float should dispatch to eval_float_method");
assert_eq!(
result.expect("operation should succeed in test"),
Value::Float(2.0),
"sqrt(4.0) should be 2.0"
);
}
#[test]
fn test_eval_method_call_logical_operator() {
let df_val = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "test".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
}],
};
let result = dispatch_method_call(&df_val, "columns", &[], true);
assert!(
result.is_ok(),
"DataFrame with non-filter method should use normal dispatch"
);
match result.expect("operation should succeed in test") {
Value::Array(_) => {} _ => panic!("DataFrame.columns should return array via normal dispatch"),
}
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_float_to_int() {
assert_eq!(
eval_float_method(3.7, "to_int", true).expect("operation should succeed in test"),
Value::Integer(3)
);
assert_eq!(
eval_float_method(-2.9, "to_int", true).expect("operation should succeed in test"),
Value::Integer(-2)
);
assert_eq!(
eval_float_method(0.0, "to_int", true).expect("operation should succeed in test"),
Value::Integer(0)
);
}
#[test]
fn test_float_to_integer_alias() {
assert_eq!(
eval_float_method(5.5, "to_integer", true).expect("operation should succeed in test"),
Value::Integer(5)
);
}
#[test]
fn test_float_to_string() {
let result =
eval_float_method(3.14, "to_string", true).expect("operation should succeed in test");
match result {
Value::String(s) => assert!(s.contains("3.14")),
_ => panic!("Expected string value"),
}
}
#[test]
fn test_float_powf_error() {
let result = eval_float_method(2.0, "powf", true);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
InterpreterError::RuntimeError(msg) => {
assert!(msg.contains("Use ** operator"));
}
_ => panic!("Expected RuntimeError"),
}
}
#[test]
fn test_float_unknown_method() {
let result = eval_float_method(2.0, "unknown_method", true);
assert!(result.is_err());
match result.unwrap_err() {
InterpreterError::RuntimeError(msg) => {
assert!(msg.contains("Unknown float method"));
}
_ => panic!("Expected RuntimeError"),
}
}
#[test]
fn test_integer_unknown_method() {
let result = eval_integer_method(42, "unknown_method", &[]);
assert!(result.is_err());
match result.unwrap_err() {
InterpreterError::RuntimeError(msg) => {
assert!(msg.contains("Unknown integer method"));
}
_ => panic!("Expected RuntimeError"),
}
}
#[test]
fn test_array_slice() {
let arr = Arc::from(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
Value::Integer(4),
Value::Integer(5),
]);
let result =
eval_array_method_simple(&arr, "slice", &[Value::Integer(1), Value::Integer(4)])
.expect("operation should succeed in test");
match result {
Value::Array(sliced) => {
assert_eq!(sliced.len(), 3);
assert_eq!(sliced[0], Value::Integer(2));
assert_eq!(sliced[1], Value::Integer(3));
assert_eq!(sliced[2], Value::Integer(4));
}
_ => panic!("Expected array"),
}
}
#[test]
fn test_array_slice_edge_cases() {
let arr = Arc::from(vec![Value::Integer(1), Value::Integer(2)]);
let result =
eval_array_method_simple(&arr, "slice", &[Value::Integer(5), Value::Integer(10)])
.expect("operation should succeed in test");
match result {
Value::Array(sliced) => assert!(sliced.is_empty()),
_ => panic!("Expected empty array"),
}
let result =
eval_array_method_simple(&arr, "slice", &[Value::Integer(-1), Value::Integer(1)])
.expect("operation should succeed in test");
match result {
Value::Array(sliced) => assert_eq!(sliced.len(), 1),
_ => panic!("Expected array with one element"),
}
}
#[test]
fn test_array_slice_wrong_types() {
let arr = Arc::from(vec![Value::Integer(1)]);
let result = eval_array_method_simple(
&arr,
"slice",
&[Value::String("a".into()), Value::Integer(1)],
);
assert!(result.is_err());
}
#[test]
fn test_array_join() {
let arr = Arc::from(vec![
Value::from_string("a".to_string()),
Value::from_string("b".to_string()),
Value::from_string("c".to_string()),
]);
let result =
eval_array_method_simple(&arr, "join", &[Value::from_string(", ".to_string())])
.expect("operation should succeed in test");
match result {
Value::String(s) => assert_eq!(s.as_ref(), "a, b, c"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_array_join_integers() {
let arr = Arc::from(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]);
let result = eval_array_method_simple(&arr, "join", &[Value::from_string("-".to_string())])
.expect("operation should succeed in test");
match result {
Value::String(s) => assert_eq!(s.as_ref(), "1-2-3"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_array_join_wrong_type() {
let arr = Arc::from(vec![Value::Integer(1)]);
let result = eval_array_method_simple(&arr, "join", &[Value::Integer(1)]);
assert!(result.is_err());
}
#[test]
fn test_array_unique() {
let arr = Arc::from(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(1),
Value::Integer(3),
Value::Integer(2),
]);
let result = eval_array_method_simple(&arr, "unique", &[])
.expect("operation should succeed in test");
match result {
Value::Array(unique) => {
assert_eq!(unique.len(), 3);
assert_eq!(unique[0], Value::Integer(1));
assert_eq!(unique[1], Value::Integer(2));
assert_eq!(unique[2], Value::Integer(3));
}
_ => panic!("Expected array"),
}
}
#[test]
fn test_array_unknown_method() {
let arr = Arc::from(vec![Value::Integer(1)]);
let result = eval_array_method_simple(&arr, "unknown_method", &[]);
assert!(result.is_err());
match result.unwrap_err() {
InterpreterError::RuntimeError(msg) => {
assert!(msg.contains("requires interpreter context"));
}
_ => panic!("Expected RuntimeError"),
}
}
#[test]
fn test_dataframe_shape() {
let columns = vec![
DataFrameColumn {
name: "col1".to_string(),
values: vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
},
DataFrameColumn {
name: "col2".to_string(),
values: vec![Value::Integer(4), Value::Integer(5), Value::Integer(6)],
},
];
let result = eval_dataframe_method_simple(&columns, "shape", &[])
.expect("operation should succeed in test");
match result {
Value::Tuple(tuple) => {
assert_eq!(tuple.len(), 2);
assert_eq!(tuple[0], Value::Integer(3)); assert_eq!(tuple[1], Value::Integer(2)); }
_ => panic!("Expected tuple"),
}
}
#[test]
fn test_dataframe_shape_empty() {
let columns: Vec<DataFrameColumn> = vec![];
let result = eval_dataframe_method_simple(&columns, "shape", &[])
.expect("operation should succeed in test");
match result {
Value::Tuple(tuple) => {
assert_eq!(tuple.len(), 2);
assert_eq!(tuple[0], Value::Integer(0)); assert_eq!(tuple[1], Value::Integer(0)); }
_ => panic!("Expected tuple"),
}
}
#[test]
fn test_dataframe_shape_rejects_args() {
let columns = vec![DataFrameColumn {
name: "col1".to_string(),
values: vec![Value::Integer(1)],
}];
let result = eval_dataframe_method_simple(&columns, "shape", &[Value::Integer(1)]);
assert!(result.is_err());
}
#[test]
fn test_dispatch_string() {
let s = Value::from_string("hello".to_string());
let result = dispatch_method_call(&s, "len", &[], true);
assert!(result.is_ok());
assert_eq!(
result.expect("operation should succeed in test"),
Value::Integer(5)
);
}
#[test]
fn test_dispatch_integer() {
let i = Value::Integer(-42);
let result = dispatch_method_call(&i, "abs", &[], true);
assert!(result.is_ok());
assert_eq!(
result.expect("operation should succeed in test"),
Value::Integer(42)
);
}
#[test]
fn test_dispatch_generic_fallback() {
let nil = Value::Nil;
let result = dispatch_method_call(&nil, "to_string", &[], true);
assert!(result.is_ok());
match result.expect("operation should succeed in test") {
Value::String(s) => assert_eq!(s.as_ref(), "nil"),
_ => panic!("Expected string"),
}
}
}
#[cfg(test)]
mod round_133_tests {
use super::*;
#[test]
fn test_float_sqrt_zero() {
assert_eq!(
eval_float_method(0.0, "sqrt", true).unwrap(),
Value::Float(0.0)
);
}
#[test]
fn test_float_sqrt_one() {
assert_eq!(
eval_float_method(1.0, "sqrt", true).unwrap(),
Value::Float(1.0)
);
}
#[test]
fn test_float_abs_zero() {
assert_eq!(
eval_float_method(0.0, "abs", true).unwrap(),
Value::Float(0.0)
);
}
#[test]
fn test_float_abs_positive() {
assert_eq!(
eval_float_method(5.5, "abs", true).unwrap(),
Value::Float(5.5)
);
}
#[test]
fn test_float_round_half() {
let result = eval_float_method(2.5, "round", true).unwrap();
assert!(matches!(result, Value::Float(_)));
}
#[test]
fn test_float_floor_positive() {
assert_eq!(
eval_float_method(3.9, "floor", true).unwrap(),
Value::Float(3.0)
);
}
#[test]
fn test_float_floor_negative() {
assert_eq!(
eval_float_method(-3.1, "floor", true).unwrap(),
Value::Float(-4.0)
);
}
#[test]
fn test_float_ceil_positive() {
assert_eq!(
eval_float_method(3.1, "ceil", true).unwrap(),
Value::Float(4.0)
);
}
#[test]
fn test_float_ceil_negative() {
assert_eq!(
eval_float_method(-3.9, "ceil", true).unwrap(),
Value::Float(-3.0)
);
}
#[test]
fn test_float_to_int_large() {
let result = eval_float_method(1_000_000.5, "to_int", true).unwrap();
assert_eq!(result, Value::Integer(1_000_000));
}
#[test]
fn test_integer_abs_zero() {
assert_eq!(
eval_integer_method(0, "abs", &[]).unwrap(),
Value::Integer(0)
);
}
#[test]
fn test_integer_abs_max() {
assert_eq!(
eval_integer_method(i64::MAX, "abs", &[]).unwrap(),
Value::Integer(i64::MAX)
);
}
#[test]
fn test_integer_pow_zero_exp() {
assert_eq!(
eval_integer_method(100, "pow", &[Value::Integer(0)]).unwrap(),
Value::Integer(1)
);
}
#[test]
fn test_integer_pow_one_exp() {
assert_eq!(
eval_integer_method(42, "pow", &[Value::Integer(1)]).unwrap(),
Value::Integer(42)
);
}
#[test]
fn test_integer_pow_large_exp() {
assert_eq!(
eval_integer_method(2, "pow", &[Value::Integer(10)]).unwrap(),
Value::Integer(1024)
);
}
#[test]
fn test_integer_to_string_zero() {
let result = eval_integer_method(0, "to_string", &[]).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "0"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_integer_to_string_negative() {
let result = eval_integer_method(-123, "to_string", &[]).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "-123"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_array_len_empty() {
let arr = Arc::from(vec![]);
assert_eq!(
eval_array_method_simple(&arr, "len", &[]).unwrap(),
Value::Integer(0)
);
}
#[test]
fn test_array_slice_same_indices() {
let arr = Arc::from(vec![Value::Integer(1), Value::Integer(2)]);
let result =
eval_array_method_simple(&arr, "slice", &[Value::Integer(1), Value::Integer(1)])
.unwrap();
match result {
Value::Array(sliced) => assert!(sliced.is_empty()),
_ => panic!("Expected empty array"),
}
}
#[test]
fn test_array_join_empty() {
let arr = Arc::from(vec![]);
let result =
eval_array_method_simple(&arr, "join", &[Value::from_string(",".to_string())]).unwrap();
match result {
Value::String(s) => assert!(s.is_empty()),
_ => panic!("Expected empty string"),
}
}
#[test]
fn test_array_join_single() {
let arr = Arc::from(vec![Value::from_string("only".to_string())]);
let result =
eval_array_method_simple(&arr, "join", &[Value::from_string(",".to_string())]).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "only"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_array_unique_all_same() {
let arr = Arc::from(vec![
Value::Integer(1),
Value::Integer(1),
Value::Integer(1),
]);
let result = eval_array_method_simple(&arr, "unique", &[]).unwrap();
match result {
Value::Array(unique) => assert_eq!(unique.len(), 1),
_ => panic!("Expected array"),
}
}
#[test]
fn test_array_unique_empty() {
let arr = Arc::from(vec![]);
let result = eval_array_method_simple(&arr, "unique", &[]).unwrap();
match result {
Value::Array(unique) => assert!(unique.is_empty()),
_ => panic!("Expected empty array"),
}
}
#[test]
fn test_generic_to_string_bool_true() {
let val = Value::Bool(true);
let result = eval_generic_method(&val, "to_string", true).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "true"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_generic_to_string_bool_false() {
let val = Value::Bool(false);
let result = eval_generic_method(&val, "to_string", true).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "false"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_generic_method_with_args_error() {
let val = Value::Nil;
let result = eval_generic_method(&val, "to_string", false);
assert!(result.is_err());
}
#[test]
fn test_generic_unknown_method() {
let val = Value::Tuple(Arc::from(vec![Value::Integer(1)].as_slice()));
let result = eval_generic_method(&val, "some_method", true);
assert!(result.is_err());
}
#[test]
fn test_dataframe_columns_empty() {
let columns: Vec<DataFrameColumn> = vec![];
let result = eval_dataframe_method_simple(&columns, "columns", &[]).unwrap();
match result {
Value::Array(arr) => assert!(arr.is_empty()),
_ => panic!("Expected empty array"),
}
}
#[test]
fn test_dataframe_columns_single() {
let columns = vec![DataFrameColumn {
name: "only_col".to_string(),
values: vec![Value::Integer(1)],
}];
let result = eval_dataframe_method_simple(&columns, "columns", &[]).unwrap();
match result {
Value::Array(arr) => {
assert_eq!(arr.len(), 1);
assert_eq!(arr[0], Value::from_string("only_col".to_string()));
}
_ => panic!("Expected array"),
}
}
#[test]
fn test_dispatch_bool_to_string() {
let b = Value::Bool(true);
let result = dispatch_method_call(&b, "to_string", &[], true).unwrap();
match result {
Value::String(s) => assert_eq!(s.as_ref(), "true"),
_ => panic!("Expected string"),
}
}
#[test]
fn test_dispatch_tuple_to_string() {
let t = Value::Tuple(Arc::from(
vec![Value::Integer(1), Value::Integer(2)].as_slice(),
));
let result = dispatch_method_call(&t, "to_string", &[], true).unwrap();
match result {
Value::String(s) => assert!(s.contains("1") && s.contains("2")),
_ => panic!("Expected string"),
}
}
#[test]
fn test_dispatch_nil_unknown_method() {
let nil = Value::Nil;
let result = dispatch_method_call(&nil, "unknown", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_string_len_method() {
let s = Value::from_string("hello".to_string());
let result = dispatch_method_call(&s, "len", &[], true).unwrap();
assert_eq!(result, Value::Integer(5));
}
#[test]
fn test_dispatch_string_is_empty_false() {
let s = Value::from_string("hello".to_string());
let result = dispatch_method_call(&s, "is_empty", &[], true).unwrap();
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_dispatch_string_is_empty_true() {
let s = Value::from_string(String::new());
let result = dispatch_method_call(&s, "is_empty", &[], true).unwrap();
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_dispatch_array_len_method() {
let arr = Value::Array(Arc::from(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]));
let result = dispatch_method_call(&arr, "len", &[], true).unwrap();
assert_eq!(result, Value::Integer(3));
}
#[test]
fn test_dispatch_array_is_empty_false() {
let arr = Value::Array(Arc::from(vec![Value::Integer(1)]));
let result = dispatch_method_call(&arr, "is_empty", &[], true).unwrap();
assert_eq!(result, Value::Bool(false));
}
#[test]
fn test_dispatch_array_is_empty_true() {
let arr = Value::Array(Arc::from(vec![]));
let result = dispatch_method_call(&arr, "is_empty", &[], true).unwrap();
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_dispatch_float_floor_method() {
let f = Value::Float(3.7);
let result = dispatch_method_call(&f, "floor", &[], true).unwrap();
assert_eq!(result, Value::Float(3.0));
}
#[test]
fn test_dispatch_float_ceil_method() {
let f = Value::Float(3.2);
let result = dispatch_method_call(&f, "ceil", &[], true).unwrap();
assert_eq!(result, Value::Float(4.0));
}
#[test]
fn test_dispatch_float_round_method() {
let f = Value::Float(3.5);
let result = dispatch_method_call(&f, "round", &[], true).unwrap();
assert_eq!(result, Value::Float(4.0));
}
#[test]
fn test_dispatch_float_abs_method() {
let f = Value::Float(-3.5);
let result = dispatch_method_call(&f, "abs", &[], true).unwrap();
assert_eq!(result, Value::Float(3.5));
}
#[test]
fn test_dispatch_integer_abs_method() {
let n = Value::Integer(-42);
let result = dispatch_method_call(&n, "abs", &[], true).unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_dispatch_integer_to_string_method() {
let n = Value::Integer(123);
let result = dispatch_method_call(&n, "to_string", &[], true).unwrap();
match result {
Value::String(s) => assert!(s.contains("123")),
_ => panic!("Expected string"),
}
}
#[test]
fn test_dispatch_float_powf_error() {
let f = Value::Float(2.0);
let result = dispatch_method_call(&f, "powf", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_bool_true_to_string() {
let b = Value::Bool(true);
let result = dispatch_method_call(&b, "to_string", &[], true).unwrap();
match result {
Value::String(s) => assert!(s.contains("true")),
_ => panic!("Expected string"),
}
}
}
#[cfg(test)]
mod eval_method_call_tests {
use super::*;
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
fn make_expr(kind: ExprKind) -> Expr {
Expr::new(kind, Span::default())
}
#[test]
fn test_eval_method_call_string_len() {
let receiver = make_expr(ExprKind::Literal(Literal::String("hello".to_string())));
let args: Vec<Expr> = vec![];
let mut eval_fn = |expr: &Expr| -> Result<Value, InterpreterError> {
match &expr.kind {
ExprKind::Literal(Literal::String(s)) => Ok(Value::from_string(s.clone())),
_ => Ok(Value::Nil),
}
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called for string receiver");
};
let result = eval_method_call(&receiver, "len", &args, &mut eval_fn, &mut df_filter)
.expect("string len should succeed");
assert_eq!(result, Value::Integer(5));
}
#[test]
fn test_eval_method_call_integer_abs() {
let receiver = make_expr(ExprKind::Literal(Literal::Integer(-42, None)));
let args: Vec<Expr> = vec![];
let mut eval_fn = |expr: &Expr| -> Result<Value, InterpreterError> {
match &expr.kind {
ExprKind::Literal(Literal::Integer(n, _)) => Ok(Value::Integer(*n)),
_ => Ok(Value::Nil),
}
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called for integer receiver");
};
let result = eval_method_call(&receiver, "abs", &args, &mut eval_fn, &mut df_filter)
.expect("integer abs should succeed");
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_eval_method_call_float_sqrt() {
let receiver = make_expr(ExprKind::Literal(Literal::Float(9.0)));
let args: Vec<Expr> = vec![];
let mut eval_fn = |expr: &Expr| -> Result<Value, InterpreterError> {
match &expr.kind {
ExprKind::Literal(Literal::Float(f)) => Ok(Value::Float(*f)),
_ => Ok(Value::Nil),
}
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called for float receiver");
};
let result = eval_method_call(&receiver, "sqrt", &args, &mut eval_fn, &mut df_filter)
.expect("float sqrt should succeed");
assert_eq!(result, Value::Float(3.0));
}
#[test]
fn test_eval_method_call_with_args() {
let receiver = make_expr(ExprKind::Literal(Literal::Integer(2, None)));
let args = vec![make_expr(ExprKind::Literal(Literal::Integer(3, None)))];
let mut eval_fn = |expr: &Expr| -> Result<Value, InterpreterError> {
match &expr.kind {
ExprKind::Literal(Literal::Integer(n, _)) => Ok(Value::Integer(*n)),
_ => Ok(Value::Nil),
}
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called for integer receiver");
};
let result = eval_method_call(&receiver, "pow", &args, &mut eval_fn, &mut df_filter)
.expect("integer pow should succeed");
assert_eq!(result, Value::Integer(8));
}
#[test]
fn test_eval_method_call_receiver_eval_error() {
let receiver = make_expr(ExprKind::Identifier("undefined".to_string()));
let args: Vec<Expr> = vec![];
let mut eval_fn = |_expr: &Expr| -> Result<Value, InterpreterError> {
Err(InterpreterError::RuntimeError("undefined variable".to_string()))
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called when receiver eval fails");
};
let result = eval_method_call(&receiver, "len", &args, &mut eval_fn, &mut df_filter);
assert!(result.is_err());
}
#[test]
fn test_eval_method_call_dataframe_filter_dispatches() {
let receiver = make_expr(ExprKind::Literal(Literal::Integer(0, None)));
let args = vec![make_expr(ExprKind::Literal(Literal::Bool(true)))];
let columns = vec![DataFrameColumn {
name: "a".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
}];
let mut eval_fn = |_expr: &Expr| -> Result<Value, InterpreterError> {
Ok(Value::DataFrame {
columns: columns.clone(),
})
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
Ok(Value::DataFrame {
columns: vec![DataFrameColumn {
name: "a".to_string(),
values: vec![Value::Integer(1)],
}],
})
};
let result = eval_method_call(&receiver, "filter", &args, &mut eval_fn, &mut df_filter)
.expect("dataframe filter should succeed");
match result {
Value::DataFrame { columns } => {
assert_eq!(columns.len(), 1);
assert_eq!(columns[0].values.len(), 1);
}
_ => panic!("Expected DataFrame"),
}
}
#[test]
fn test_eval_method_call_arg_eval_error() {
let receiver = make_expr(ExprKind::Literal(Literal::Integer(2, None)));
let args = vec![make_expr(ExprKind::Identifier("bad_arg".to_string()))];
let mut call_count = 0;
let mut eval_fn = |expr: &Expr| -> Result<Value, InterpreterError> {
call_count += 1;
if call_count == 1 {
Ok(Value::Integer(2))
} else {
Err(InterpreterError::RuntimeError("arg eval error".to_string()))
}
};
let mut df_filter = |_val: &Value, _args: &[Expr]| -> Result<Value, InterpreterError> {
panic!("should not be called");
};
let result = eval_method_call(&receiver, "pow", &args, &mut eval_fn, &mut df_filter);
assert!(result.is_err());
}
}