#![allow(clippy::unused_self)]
#![allow(clippy::only_used_in_recursion)]
#![allow(clippy::expect_used)]
use crate::runtime::interpreter::Interpreter;
use crate::runtime::{InterpreterError, Value};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
impl Interpreter {
pub(crate) fn eval_actor_instance_method_mut(
&mut self,
cell_rc: &Arc<std::sync::Mutex<std::collections::HashMap<String, Value>>>,
actor_name: &str,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
if method == "send" {
if arg_values.is_empty() {
return Err(InterpreterError::RuntimeError(
"send() requires a message argument".to_string(),
));
}
return self.process_actor_message_sync_mut(cell_rc, &arg_values[0]);
}
let instance = cell_rc
.lock()
.expect("Mutex poisoned: instance lock is corrupted");
self.eval_actor_instance_method(&instance, actor_name, method, arg_values)
}
pub(crate) fn eval_class_instance_method_mut(
&mut self,
cell_rc: &Arc<std::sync::Mutex<std::collections::HashMap<String, Value>>>,
class_name: &str,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
let class_def = self.lookup_variable(class_name)?;
if let Value::Object(ref class_info) = class_def {
if let Some(Value::Object(ref methods)) = class_info.get("__methods") {
if let Some(Value::Object(ref method_meta)) = methods.get(method) {
if let Some(Value::Closure { params, body, .. }) = method_meta.get("closure") {
let is_static = method_meta
.get("is_static")
.and_then(|v| {
if let Value::Bool(b) = v {
Some(*b)
} else {
None
}
})
.unwrap_or(false);
if is_static {
return Err(InterpreterError::RuntimeError(format!(
"Cannot call static method {} on instance",
method
)));
}
let mut method_env = HashMap::new();
method_env
.insert("self".to_string(), Value::ObjectMut(Arc::clone(cell_rc)));
if arg_values.len() != params.len() {
return Err(InterpreterError::RuntimeError(format!(
"Method {} expects {} arguments, got {}",
method,
params.len(),
arg_values.len()
)));
}
for ((param_name, _default_value), arg) in params.iter().zip(arg_values) {
method_env.insert(param_name.clone(), arg.clone());
}
self.env_push(method_env);
let result = self.eval_expr(body)?;
self.env_pop();
return Ok(result);
}
}
}
Err(InterpreterError::RuntimeError(format!(
"Class {} has no method named {}",
class_name, method
)))
} else {
Err(InterpreterError::RuntimeError(format!(
"{} is not a class",
class_name
)))
}
}
pub(crate) fn eval_class_instance_method_on_class(
&mut self,
class_name: &str,
fields: &Arc<std::sync::RwLock<HashMap<String, Value>>>,
methods: &Arc<HashMap<String, Value>>,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
if let Some(method_closure) = methods.get(method) {
if let Value::Closure { params, body, .. } = method_closure {
if arg_values.len() != params.len() {
return Err(InterpreterError::RuntimeError(format!(
"Method {} expects {} arguments, got {}",
method,
params.len(),
arg_values.len()
)));
}
let mut method_env = HashMap::new();
method_env.insert(
"self".to_string(),
Value::Class {
class_name: class_name.to_string(),
fields: Arc::clone(fields),
methods: Arc::clone(methods),
},
);
for ((param_name, _default_value), arg) in params.iter().zip(arg_values) {
method_env.insert(param_name.clone(), arg.clone());
}
self.env_push(method_env);
let result = self.eval_expr(body)?;
self.env_pop();
Ok(result)
} else {
Err(InterpreterError::RuntimeError(format!(
"Method {} is not a closure",
method
)))
}
} else {
Err(InterpreterError::RuntimeError(format!(
"Method '{}' not found for type class",
method
)))
}
}
pub(crate) fn eval_struct_instance_method_mut(
&mut self,
cell_rc: &Arc<std::sync::Mutex<std::collections::HashMap<String, Value>>>,
struct_name: &str,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
let instance_data = {
let locked = cell_rc
.lock()
.expect("Mutex poisoned: cell lock is corrupted");
locked.clone()
};
let (result, modified_self_opt) = self.eval_struct_instance_method_with_self_capture(
&instance_data,
struct_name,
method,
arg_values,
)?;
if let Some(modified_fields) = modified_self_opt {
let mut locked = cell_rc
.lock()
.expect("Mutex poisoned: cell lock is corrupted");
for (field_name, field_value) in modified_fields.iter() {
locked.insert(field_name.clone(), field_value.clone());
}
}
Ok(result)
}
pub(crate) fn eval_struct_instance_method_with_self_capture(
&mut self,
instance: &std::collections::HashMap<String, Value>,
struct_name: &str,
method: &str,
arg_values: &[Value],
) -> Result<
(
Value,
Option<std::sync::Arc<std::collections::HashMap<String, Value>>>,
),
InterpreterError,
> {
let qualified_method_name = format!("{}::{}", struct_name, method);
if let Ok(method_closure) = self.lookup_variable(&qualified_method_name) {
if let Value::Closure { params, body, env } = method_closure {
let expected_args = params.len();
let provided_args = arg_values.len() + 1;
if provided_args != expected_args {
return Err(InterpreterError::RuntimeError(format!(
"Method {} expects {} arguments, got {}",
method,
expected_args - 1, arg_values.len()
)));
}
let mut new_env = env.borrow().clone();
let self_param_name = if let Some((name, _)) = params.first() {
new_env.insert(
name.clone(),
Value::Struct {
name: struct_name.to_string(),
fields: std::sync::Arc::new(instance.clone()),
},
);
name.clone()
} else {
return Err(InterpreterError::RuntimeError(
"Method has no self parameter".to_string(),
));
};
for (i, arg_value) in arg_values.iter().enumerate() {
if let Some((param_name, _)) = params.get(i + 1) {
new_env.insert(param_name.clone(), arg_value.clone());
}
}
self.env_stack.push(Rc::new(RefCell::new(new_env)));
let result = self.eval_expr(&body);
let modified_self = if let Some(env_rc) = self.env_stack.last() {
let env_ref = env_rc.borrow();
if let Some(Value::Struct { fields, .. }) = env_ref.get(&self_param_name) {
Some(fields.clone())
} else {
None
}
} else {
None
};
self.env_stack.pop();
result.map(|r| (r, modified_self))
} else {
Err(InterpreterError::RuntimeError(format!(
"Found {} but it's not a method closure",
qualified_method_name
)))
}
} else {
let result = self.eval_generic_method(
&Value::Object(std::sync::Arc::new(instance.clone())),
method,
arg_values.is_empty(),
)?;
Ok((result, None))
}
}
pub(crate) fn eval_file_method_mut(
&mut self,
file_obj: &Arc<std::sync::Mutex<HashMap<String, Value>>>,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
match method {
"read_line" => {
if !arg_values.is_empty() {
return Err(InterpreterError::RuntimeError(
"read_line() takes no arguments".to_string(),
));
}
let mut obj = file_obj
.lock()
.expect("Mutex poisoned: file object lock is corrupted");
if let Some(Value::Bool(true)) = obj.get("closed") {
return Err(InterpreterError::RuntimeError(
"Cannot read from closed file".to_string(),
));
}
let position = if let Some(Value::Integer(pos)) = obj.get("position") {
*pos
} else {
0
};
let lines = if let Some(Value::Array(lines)) = obj.get("lines") {
lines.clone()
} else {
return Err(InterpreterError::RuntimeError(
"File object corrupted: missing lines".to_string(),
));
};
if position >= lines.len() as i64 {
return Ok(Value::from_string(String::new()));
}
let line = lines[position as usize].clone();
obj.insert("position".to_string(), Value::Integer(position + 1));
Ok(line)
}
"read" => {
if !arg_values.is_empty() {
return Err(InterpreterError::RuntimeError(
"read() takes no arguments".to_string(),
));
}
let obj = file_obj
.lock()
.expect("Mutex poisoned: file object lock is corrupted");
if let Some(Value::Bool(true)) = obj.get("closed") {
return Err(InterpreterError::RuntimeError(
"Cannot read from closed file".to_string(),
));
}
let lines = if let Some(Value::Array(lines)) = obj.get("lines") {
lines.clone()
} else {
return Err(InterpreterError::RuntimeError(
"File object corrupted: missing lines".to_string(),
));
};
let content: String = lines
.iter()
.filter_map(|v| {
if let Value::String(s) = v {
Some(s.to_string())
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n");
Ok(Value::from_string(content))
}
"close" => {
if !arg_values.is_empty() {
return Err(InterpreterError::RuntimeError(
"close() takes no arguments".to_string(),
));
}
let mut obj = file_obj
.lock()
.expect("Mutex poisoned: file object lock is corrupted");
obj.insert("closed".to_string(), Value::Bool(true));
Ok(Value::Nil)
}
_ => Err(InterpreterError::RuntimeError(format!(
"Unknown method '{}' on File object",
method
))),
}
}
pub(crate) fn eval_object_method_mut(
&mut self,
cell_rc: &Arc<std::sync::Mutex<std::collections::HashMap<String, Value>>>,
method: &str,
arg_values: &[Value],
args_empty: bool,
) -> Result<Value, InterpreterError> {
let instance = cell_rc
.lock()
.expect("Mutex poisoned: instance lock is corrupted");
self.eval_object_method(&instance, method, arg_values, args_empty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
fn make_interpreter() -> Interpreter {
Interpreter::new()
}
#[test]
fn test_file_read_line() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("position".to_string(), Value::Integer(0));
file_obj.insert("closed".to_string(), Value::Bool(false));
file_obj.insert(
"lines".to_string(),
Value::Array(Arc::from(vec![
Value::from_string("line1".to_string()),
Value::from_string("line2".to_string()),
Value::from_string("line3".to_string()),
])),
);
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp
.eval_file_method_mut(&file_rc, "read_line", &[])
.unwrap();
assert_eq!(result, Value::from_string("line1".to_string()));
let result = interp
.eval_file_method_mut(&file_rc, "read_line", &[])
.unwrap();
assert_eq!(result, Value::from_string("line2".to_string()));
let result = interp
.eval_file_method_mut(&file_rc, "read_line", &[])
.unwrap();
assert_eq!(result, Value::from_string("line3".to_string()));
let result = interp
.eval_file_method_mut(&file_rc, "read_line", &[])
.unwrap();
assert_eq!(result, Value::from_string("".to_string()));
}
#[test]
fn test_file_read_line_wrong_args() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("lines".to_string(), Value::Array(Arc::from(vec![])));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read_line", &[Value::Integer(1)]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no arguments"));
}
#[test]
fn test_file_read_line_closed() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(true));
file_obj.insert("lines".to_string(), Value::Array(Arc::from(vec![])));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read_line", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("closed file"));
}
#[test]
fn test_file_read_line_missing_lines() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read_line", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("corrupted"));
}
#[test]
fn test_file_read() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
file_obj.insert(
"lines".to_string(),
Value::Array(Arc::from(vec![
Value::from_string("line1".to_string()),
Value::from_string("line2".to_string()),
])),
);
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[]).unwrap();
assert_eq!(result, Value::from_string("line1\nline2".to_string()));
}
#[test]
fn test_file_read_wrong_args() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("lines".to_string(), Value::Array(Arc::from(vec![])));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[Value::Integer(1)]);
assert!(result.is_err());
}
#[test]
fn test_file_read_closed() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(true));
file_obj.insert("lines".to_string(), Value::Array(Arc::from(vec![])));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("closed file"));
}
#[test]
fn test_file_read_missing_lines() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("corrupted"));
}
#[test]
fn test_file_close() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "close", &[]).unwrap();
assert_eq!(result, Value::Nil);
let obj = file_rc.lock().unwrap();
assert_eq!(obj.get("closed"), Some(&Value::Bool(true)));
}
#[test]
fn test_file_close_wrong_args() {
let mut interp = make_interpreter();
let file_obj = HashMap::new();
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "close", &[Value::Integer(1)]);
assert!(result.is_err());
}
#[test]
fn test_file_unknown_method() {
let mut interp = make_interpreter();
let file_obj = HashMap::new();
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "unknown", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Unknown method"));
}
#[test]
fn test_object_method_mut_missing_type() {
let mut interp = make_interpreter();
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_object_method_mut(&obj_rc, "test", &[], true);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("missing __type marker"));
}
#[test]
fn test_actor_instance_method_mut_send_empty() {
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__actor".to_string(),
Value::from_string("TestActor".to_string()),
);
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_actor_instance_method_mut(&obj_rc, "TestActor", "send", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires a message"));
}
#[test]
fn test_struct_instance_method_with_self_capture_not_closure() {
let mut interp = make_interpreter();
interp.set_variable("TestStruct::method", Value::Integer(42));
let instance = HashMap::new();
let result = interp.eval_struct_instance_method_with_self_capture(
&instance,
"TestStruct",
"method",
&[],
);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("not a method closure"));
}
#[test]
fn test_struct_instance_method_mut_not_closure() {
let mut interp = make_interpreter();
interp.set_variable("TestStruct::method", Value::Integer(42));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_struct_instance_method_mut(&obj_rc, "TestStruct", "method", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("not a method closure"));
}
#[test]
fn test_class_instance_method_on_class_not_found() {
let mut interp = make_interpreter();
let fields = Arc::new(std::sync::RwLock::new(HashMap::new()));
let methods = Arc::new(HashMap::new());
let result = interp.eval_class_instance_method_on_class(
"TestClass",
&fields,
&methods,
"unknown_method",
&[],
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not found"));
}
#[test]
fn test_class_instance_method_on_class_not_closure() {
let mut interp = make_interpreter();
let fields = Arc::new(std::sync::RwLock::new(HashMap::new()));
let mut methods_map = HashMap::new();
methods_map.insert("bad_method".to_string(), Value::Integer(42)); let methods = Arc::new(methods_map);
let result = interp.eval_class_instance_method_on_class(
"TestClass",
&fields,
&methods,
"bad_method",
&[],
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not a closure"));
}
#[test]
fn test_class_instance_method_mut_not_class() {
let mut interp = make_interpreter();
interp.set_variable("NotAClass", Value::Integer(42));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_class_instance_method_mut(&obj_rc, "NotAClass", "method", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not a class"));
}
#[test]
fn test_class_instance_method_mut_method_not_found() {
let mut interp = make_interpreter();
let mut class_info = HashMap::new();
class_info.insert(
"__methods".to_string(),
Value::Object(Arc::new(HashMap::new())),
);
interp.set_variable("EmptyClass", Value::Object(Arc::new(class_info)));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result =
interp.eval_class_instance_method_mut(&obj_rc, "EmptyClass", "nonexistent", &[]);
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("no method named"));
}
#[test]
fn test_class_instance_method_mut_no_methods_map() {
let mut interp = make_interpreter();
let class_info = HashMap::new();
interp.set_variable("NoMethodsClass", Value::Object(Arc::new(class_info)));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result =
interp.eval_class_instance_method_mut(&obj_rc, "NoMethodsClass", "any_method", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no method named"));
}
#[test]
fn test_class_instance_method_mut_methods_not_object() {
let mut interp = make_interpreter();
let mut class_info = HashMap::new();
class_info.insert("__methods".to_string(), Value::Integer(42)); interp.set_variable("BadMethodsClass", Value::Object(Arc::new(class_info)));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result =
interp.eval_class_instance_method_mut(&obj_rc, "BadMethodsClass", "method", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no method named"));
}
#[test]
fn test_class_instance_method_mut_method_meta_not_object() {
let mut interp = make_interpreter();
let mut methods_map = HashMap::new();
methods_map.insert("bad_method".to_string(), Value::Integer(100));
let mut class_info = HashMap::new();
class_info.insert(
"__methods".to_string(),
Value::Object(Arc::new(methods_map)),
);
interp.set_variable("BadMetaClass", Value::Object(Arc::new(class_info)));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result =
interp.eval_class_instance_method_mut(&obj_rc, "BadMetaClass", "bad_method", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no method named"));
}
#[test]
fn test_class_instance_method_mut_no_closure_in_meta() {
let mut interp = make_interpreter();
let method_meta = HashMap::new();
let mut methods_map = HashMap::new();
methods_map.insert(
"no_closure".to_string(),
Value::Object(Arc::new(method_meta)),
);
let mut class_info = HashMap::new();
class_info.insert(
"__methods".to_string(),
Value::Object(Arc::new(methods_map)),
);
interp.set_variable("NoClosureClass", Value::Object(Arc::new(class_info)));
let obj = HashMap::new();
let obj_rc = Arc::new(Mutex::new(obj));
let result =
interp.eval_class_instance_method_mut(&obj_rc, "NoClosureClass", "no_closure", &[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("no method named"));
}
#[test]
fn test_struct_instance_method_with_self_capture_fallback() {
let mut interp = make_interpreter();
let instance = HashMap::new();
let result = interp.eval_struct_instance_method_with_self_capture(
&instance,
"NonExistentStruct",
"unknown_method",
&[],
);
let _ = result;
}
#[test]
fn test_actor_instance_method_mut_send_with_message() {
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__actor".to_string(),
Value::from_string("TestActor".to_string()),
);
obj.insert("__handlers".to_string(), Value::Array(Arc::from(vec![])));
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_actor_instance_method_mut(
&obj_rc,
"TestActor",
"send",
&[Value::Integer(42)],
);
let _ = result;
}
#[test]
fn test_actor_instance_method_mut_non_send_method() {
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__actor".to_string(),
Value::from_string("TestActor".to_string()),
);
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_actor_instance_method_mut(&obj_rc, "TestActor", "stop", &[]);
assert!(result.is_ok());
}
#[test]
fn test_object_method_mut_with_type_marker() {
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__type".to_string(),
Value::from_string("HashMap".to_string()),
);
obj.insert("key".to_string(), Value::Integer(42));
let obj_rc = Arc::new(Mutex::new(obj));
let _result = interp.eval_object_method_mut(&obj_rc, "keys", &[], true);
}
#[test]
fn test_object_method_mut_with_args() {
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__type".to_string(),
Value::from_string("HashMap".to_string()),
);
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_object_method_mut(
&obj_rc,
"get",
&[Value::from_string("key".to_string())],
false,
);
let _ = result;
}
#[test]
fn test_file_read_line_no_position() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
file_obj.insert(
"lines".to_string(),
Value::Array(Arc::from(vec![Value::from_string("first".to_string())])),
);
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp
.eval_file_method_mut(&file_rc, "read_line", &[])
.unwrap();
assert_eq!(result, Value::from_string("first".to_string()));
}
#[test]
fn test_file_read_with_non_string_lines() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
file_obj.insert(
"lines".to_string(),
Value::Array(Arc::from(vec![
Value::from_string("line1".to_string()),
Value::Integer(42), Value::from_string("line2".to_string()),
])),
);
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[]).unwrap();
assert_eq!(result, Value::from_string("line1\nline2".to_string()));
}
#[test]
fn test_file_read_empty_file() {
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
file_obj.insert("closed".to_string(), Value::Bool(false));
file_obj.insert("lines".to_string(), Value::Array(Arc::from(vec![])));
let file_rc = Arc::new(Mutex::new(file_obj));
let result = interp.eval_file_method_mut(&file_rc, "read", &[]).unwrap();
assert_eq!(result, Value::from_string(String::new()));
}
#[test]
fn test_class_method_dispatch_via_eval_string() {
let mut interp = make_interpreter();
let result = interp.eval_string(
r#"
class Counter {
fn new() {
self.value = 0
}
}
"#,
);
assert!(result.is_ok());
}
#[test]
fn test_struct_method_via_eval_string() {
let mut interp = make_interpreter();
let result = interp.eval_string(
r#"
struct Point { x, y }
"#,
);
let _ = result;
}
#[test]
fn test_class_instance_method_on_class_wrong_arg_count() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let fields = Arc::new(std::sync::RwLock::new(HashMap::new()));
let closure = Value::Closure {
params: vec![("a".to_string(), None), ("b".to_string(), None)],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
let mut methods_map = HashMap::new();
methods_map.insert("two_params".to_string(), closure);
let methods = Arc::new(methods_map);
let result = interp.eval_class_instance_method_on_class(
"TestClass",
&fields,
&methods,
"two_params",
&[Value::Integer(1)], );
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("expects 2 arguments"));
}
#[test]
fn test_class_instance_method_on_class_success() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let fields = Arc::new(std::sync::RwLock::new(HashMap::new()));
let closure = Value::Closure {
params: vec![],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
let mut methods_map = HashMap::new();
methods_map.insert("get_42".to_string(), closure);
let methods = Arc::new(methods_map);
let result = interp.eval_class_instance_method_on_class(
"TestClass",
&fields,
&methods,
"get_42",
&[],
);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(42));
}
#[test]
fn test_struct_instance_method_with_self_capture_wrong_arg_count() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let closure = Value::Closure {
params: vec![
("self".to_string(), None),
("a".to_string(), None),
("b".to_string(), None),
],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
interp.set_variable("TestStruct::my_method", closure);
let instance = HashMap::new();
let result = interp.eval_struct_instance_method_with_self_capture(
&instance,
"TestStruct",
"my_method",
&[Value::Integer(1)], );
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("expects 2 arguments"));
}
#[test]
fn test_struct_instance_method_with_self_capture_one_param_wrong_count() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let closure = Value::Closure {
params: vec![("self".to_string(), None)],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
interp.set_variable("TestStruct::one_param", closure);
let instance = HashMap::new();
let result = interp.eval_struct_instance_method_with_self_capture(
&instance,
"TestStruct",
"one_param",
&[Value::Integer(1)], );
assert!(result.is_err());
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.contains("expects 0 arguments"));
}
#[test]
fn test_struct_instance_method_with_self_capture_success() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let closure = Value::Closure {
params: vec![("self".to_string(), None)],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
interp.set_variable("TestStruct::simple_method", closure);
let mut instance = HashMap::new();
instance.insert("field1".to_string(), Value::Integer(10));
let result = interp.eval_struct_instance_method_with_self_capture(
&instance,
"TestStruct",
"simple_method",
&[],
);
assert!(result.is_ok());
let (value, _modified_self) = result.unwrap();
assert_eq!(value, Value::Integer(42));
}
#[test]
fn test_struct_instance_method_mut_success_path() {
use crate::frontend::ast::{Expr, ExprKind, Literal, Span};
let mut interp = make_interpreter();
let closure = Value::Closure {
params: vec![("self".to_string(), None)],
body: Arc::new(Expr::new(
ExprKind::Literal(Literal::Integer(42, None)),
Span::new(0, 0),
)),
env: Rc::new(RefCell::new(HashMap::new())),
};
interp.set_variable("MyStruct::get_value", closure);
let mut obj = HashMap::new();
obj.insert("value".to_string(), Value::Integer(100));
let obj_rc = Arc::new(Mutex::new(obj));
let result = interp.eval_struct_instance_method_mut(&obj_rc, "MyStruct", "get_value", &[]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(42));
}
}