#![allow(clippy::unused_self)]
#![allow(clippy::only_used_in_recursion)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::expect_used)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::rc_buffer)]
use crate::frontend::ast::{Expr, ExprKind};
use crate::runtime::interpreter::Interpreter;
use crate::runtime::{InterpreterError, Value};
use std::collections::HashMap;
use std::sync::Arc;
impl Interpreter {
pub(crate) fn eval_method_call(
&mut self,
receiver: &Expr,
method: &str,
args: &[Expr],
) -> Result<Value, InterpreterError> {
if let ExprKind::Identifier(namespace) = &receiver.kind {
let namespace_method = format!("{namespace}_{method}");
let arg_values: Result<Vec<_>, _> =
args.iter().map(|arg| self.eval_expr(arg)).collect();
let arg_values = arg_values?;
if let Ok(Some(result)) =
crate::runtime::eval_builtin::eval_builtin_function(&namespace_method, &arg_values)
{
return Ok(result);
}
}
if let ExprKind::Identifier(var_name) = &receiver.kind {
if method == "push" && args.len() == 1 {
if let Ok(Value::Array(arr)) = self.lookup_variable(var_name) {
let arg_value = self.eval_expr(&args[0])?;
let mut new_arr = arr.to_vec();
new_arr.push(arg_value);
self.env_set_mut(var_name.clone(), Value::Array(Arc::from(new_arr)));
return Ok(Value::Nil); }
} else if method == "pop" && args.is_empty() {
if let Ok(Value::Array(arr)) = self.lookup_variable(var_name) {
let mut new_arr = arr.to_vec();
let popped_value = new_arr.pop().unwrap_or(Value::Nil);
self.env_set_mut(var_name.clone(), Value::Array(Arc::from(new_arr)));
return Ok(popped_value); }
}
}
if let ExprKind::FieldAccess { object, field } = &receiver.kind {
if let Ok(object_value) = self.eval_expr(object) {
if let Value::ObjectMut(cell_rc) = object_value {
if method == "push" && args.len() == 1 {
let arg_value = self.eval_expr(&args[0])?;
let mut obj = cell_rc
.lock()
.expect("Mutex poisoned: object lock is corrupted");
if let Some(field_value) = obj.get(field) {
if let Value::Array(arr) = field_value {
let mut new_arr = arr.to_vec();
new_arr.push(arg_value);
obj.insert(field.clone(), Value::Array(Arc::from(new_arr)));
return Ok(Value::Nil); }
}
}
}
}
}
let receiver_value = self.eval_expr(receiver)?;
if let Value::Object(ref obj) = receiver_value {
if let Some(Value::String(type_name)) = obj.get("__type") {
if type_name.as_ref() == "Module" {
let func_value = obj.get(method).ok_or_else(|| {
InterpreterError::RuntimeError(format!(
"Module has no function named '{}'",
method
))
})?;
let arg_values: Result<Vec<_>, _> =
args.iter().map(|arg| self.eval_expr(arg)).collect();
let arg_values = arg_values?;
return self.call_function(func_value.clone(), &arg_values);
}
}
}
if matches!(receiver_value, Value::DataFrame { .. }) {
match method {
"filter" => return self.eval_dataframe_filter_method(&receiver_value, args),
"with_column" => {
return self.eval_dataframe_with_column_method(&receiver_value, args)
}
"transform" => return self.eval_dataframe_transform_method(&receiver_value, args),
_ => {}
}
}
if (method == "send" || method == "ask") && args.len() == 1 {
let is_actor = match &receiver_value {
Value::Object(ref obj) => obj.contains_key("__actor"),
Value::ObjectMut(ref cell) => cell
.lock()
.expect("Mutex poisoned: object lock is corrupted")
.contains_key("__actor"),
_ => false,
};
if is_actor {
let arg_value = match &args[0].kind {
ExprKind::Identifier(name) => {
if let Ok(val) = self.lookup_variable(name) {
val
} else {
let mut message = HashMap::new();
message.insert(
"__type".to_string(),
Value::from_string("Message".to_string()),
);
message.insert("type".to_string(), Value::from_string(name.clone()));
message.insert("data".to_string(), Value::Array(Arc::from(vec![])));
Value::Object(Arc::new(message))
}
}
_ => self.eval_expr(&args[0])?,
};
return self.dispatch_method_call(&receiver_value, method, &[arg_value], false);
}
}
let arg_values: Result<Vec<_>, _> = args.iter().map(|arg| self.eval_expr(arg)).collect();
let arg_values = arg_values?;
if let ExprKind::Identifier(var_name) = &receiver.kind {
if matches!(receiver_value, Value::ObjectMut(_)) {
let result = self.dispatch_method_call(
&receiver_value,
method,
&arg_values,
args.is_empty(),
)?;
self.set_variable(var_name, receiver_value);
return Ok(result);
}
if let Value::Struct { name, fields } = &receiver_value {
let qualified_method_name = format!("{}::{}", name, method);
if self.lookup_variable(&qualified_method_name).is_ok() {
let (result, modified_fields_opt) = self
.eval_struct_instance_method_with_self_capture(
fields,
name,
method,
&arg_values,
)?;
if let Some(modified_fields) = modified_fields_opt {
let new_struct = Value::Struct {
name: name.clone(),
fields: modified_fields,
};
self.set_variable(var_name, new_struct);
}
return Ok(result);
}
}
}
self.dispatch_method_call(&receiver_value, method, &arg_values, args.is_empty())
}
pub(crate) fn eval_message_expr(&mut self, message: &Expr) -> Result<Value, InterpreterError> {
match &message.kind {
ExprKind::Identifier(name) => {
if let Ok(val) = self.lookup_variable(name) {
Ok(val)
} else {
let mut msg_obj = HashMap::new();
msg_obj.insert(
"__type".to_string(),
Value::from_string("Message".to_string()),
);
msg_obj.insert("type".to_string(), Value::from_string(name.clone()));
msg_obj.insert("data".to_string(), Value::Array(Arc::from(vec![])));
Ok(Value::Object(Arc::new(msg_obj)))
}
}
_ => self.eval_expr(message),
}
}
pub(crate) fn dispatch_method_call(
&mut self,
receiver: &Value,
method: &str,
arg_values: &[Value],
args_empty: bool,
) -> Result<Value, InterpreterError> {
let base_method = if let Some(pos) = method.find("::") {
&method[..pos]
} else {
method
};
match receiver {
Value::String(s) => self.eval_string_method(s, base_method, arg_values),
Value::Array(arr) => self.eval_array_method(arr, base_method, arg_values),
Value::Float(f) => self.eval_float_method(*f, base_method, args_empty),
Value::Integer(n) => self.eval_integer_method(*n, base_method, arg_values),
Value::DataFrame { columns } => {
self.eval_dataframe_method(columns, base_method, arg_values)
}
Value::Object(obj) => {
if let Some(Value::String(actor_name)) = obj.get("__actor") {
self.eval_actor_instance_method(
obj,
actor_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(class_name)) = obj.get("__class") {
self.eval_class_instance_method(
obj,
class_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(struct_name)) =
obj.get("__struct_type").or_else(|| obj.get("__struct"))
{
self.eval_struct_instance_method(
obj,
struct_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(type_str)) = obj.get("__type") {
if type_str.as_ref() == "DataFrameBuilder" {
self.eval_dataframe_builder_method(obj, base_method, arg_values)
} else {
self.eval_object_method(obj, base_method, arg_values, args_empty)
}
} else {
self.eval_object_method(obj, base_method, arg_values, args_empty)
}
}
Value::ObjectMut(cell_rc) => {
let obj = cell_rc
.lock()
.expect("Mutex poisoned: object lock is corrupted");
if let Some(Value::String(actor_name)) = obj.get("__actor") {
let actor_name = actor_name.clone();
drop(obj); self.eval_actor_instance_method_mut(
cell_rc,
actor_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(class_name)) = obj.get("__class") {
let class_name = class_name.clone();
drop(obj); self.eval_class_instance_method_mut(
cell_rc,
class_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(struct_name)) =
obj.get("__struct_type").or_else(|| obj.get("__struct"))
{
let struct_name = struct_name.clone();
drop(obj); self.eval_struct_instance_method_mut(
cell_rc,
struct_name.as_ref(),
base_method,
arg_values,
)
}
else if let Some(Value::String(type_name)) = obj.get("__type") {
if type_name.as_ref() == "File" {
drop(obj); return self.eval_file_method_mut(cell_rc, base_method, arg_values);
}
drop(obj); self.eval_object_method_mut(cell_rc, base_method, arg_values, args_empty)
} else {
drop(obj); self.eval_object_method_mut(cell_rc, base_method, arg_values, args_empty)
}
}
Value::Struct { name, fields } => {
self.eval_struct_instance_method(fields, name, base_method, arg_values)
}
Value::Class {
class_name,
fields,
methods,
} => {
self.eval_class_instance_method_on_class(
class_name,
fields,
methods,
base_method,
arg_values,
)
}
#[cfg(not(target_arch = "wasm32"))]
Value::HtmlDocument(doc) => {
self.eval_html_document_method(doc, base_method, arg_values)
}
#[cfg(not(target_arch = "wasm32"))]
Value::HtmlElement(elem) => {
self.eval_html_element_method(elem, base_method, arg_values)
}
_ => self.eval_generic_method(receiver, base_method, args_empty),
}
}
pub(crate) fn eval_float_method(
&self,
f: f64,
method: &str,
args_empty: bool,
) -> Result<Value, InterpreterError> {
super::eval_method::eval_float_method(f, method, args_empty)
}
pub(crate) fn eval_integer_method(
&self,
n: i64,
method: &str,
arg_values: &[Value],
) -> Result<Value, InterpreterError> {
super::eval_method::eval_integer_method(n, method, arg_values)
}
pub(crate) fn eval_generic_method(
&self,
receiver: &Value,
method: &str,
args_empty: bool,
) -> Result<Value, InterpreterError> {
super::eval_method::eval_generic_method(receiver, method, args_empty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::ast::Span;
fn make_interpreter() -> Interpreter {
Interpreter::new()
}
fn make_expr(kind: ExprKind) -> Expr {
Expr {
kind,
span: Span::default(),
attributes: vec![],
leading_comments: vec![],
trailing_comment: None,
}
}
#[test]
fn test_dispatch_strips_turbofish() {
let mut interp = make_interpreter();
let s = Value::from_string("42".to_string());
let result = interp.dispatch_method_call(&s, "parse::<i32>", &[], false);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::Integer(42));
}
#[test]
fn test_dispatch_to_string_method() {
let mut interp = make_interpreter();
let s = Value::from_string("hello".to_string());
let result = interp.dispatch_method_call(&s, "len", &[], false).unwrap();
assert_eq!(result, Value::Integer(5));
}
#[test]
fn test_dispatch_to_array_method() {
let mut interp = make_interpreter();
let arr = Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)]));
let result = interp
.dispatch_method_call(&arr, "len", &[], false)
.unwrap();
assert_eq!(result, Value::Integer(2));
}
#[test]
fn test_dispatch_to_float_method() {
let mut interp = make_interpreter();
let f = Value::Float(3.7);
let result = interp.dispatch_method_call(&f, "round", &[], true).unwrap();
assert_eq!(result, Value::Float(4.0));
}
#[test]
fn test_dispatch_to_integer_method() {
let mut interp = make_interpreter();
let n = Value::Integer(-5);
let result = interp.dispatch_method_call(&n, "abs", &[], false).unwrap();
assert_eq!(result, Value::Integer(5));
}
#[test]
fn test_eval_message_expr_undefined_identifier() {
let mut interp = make_interpreter();
let expr = make_expr(ExprKind::Identifier("Increment".to_string()));
let result = interp.eval_message_expr(&expr).unwrap();
if let Value::Object(obj) = result {
assert_eq!(
obj.get("__type"),
Some(&Value::from_string("Message".to_string()))
);
assert_eq!(
obj.get("type"),
Some(&Value::from_string("Increment".to_string()))
);
} else {
panic!("Expected Object");
}
}
#[test]
fn test_eval_message_expr_defined_variable() {
let mut interp = make_interpreter();
interp.set_variable("x", Value::Integer(42));
let expr = make_expr(ExprKind::Identifier("x".to_string()));
let result = interp.eval_message_expr(&expr).unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_eval_message_expr_literal() {
let mut interp = make_interpreter();
let expr = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(
100, None,
)));
let result = interp.eval_message_expr(&expr).unwrap();
assert_eq!(result, Value::Integer(100));
}
#[test]
fn test_dispatch_to_object_missing_type() {
let mut interp = make_interpreter();
let obj_map = HashMap::new();
let obj = Value::Object(Arc::new(obj_map));
let result = interp.dispatch_method_call(&obj, "test", &[], true);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("missing __type marker"));
}
#[test]
fn test_dispatch_to_object_unknown_type() {
let mut interp = make_interpreter();
let mut obj_map = HashMap::new();
obj_map.insert(
"__type".to_string(),
Value::from_string("UnknownType".to_string()),
);
let obj = Value::Object(Arc::new(obj_map));
let result = interp.dispatch_method_call(&obj, "test", &[], true);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unknown object type"));
}
#[test]
fn test_eval_generic_method() {
let interp = make_interpreter();
let v = Value::Integer(42);
let result = interp.eval_generic_method(&v, "to_string", false);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_float_method_ceil() {
let interp = make_interpreter();
let result = interp.eval_float_method(3.2, "ceil", true).unwrap();
assert_eq!(result, Value::Float(4.0));
}
#[test]
fn test_eval_float_method_floor() {
let interp = make_interpreter();
let result = interp.eval_float_method(3.8, "floor", true).unwrap();
assert_eq!(result, Value::Float(3.0));
}
#[test]
fn test_eval_integer_method_abs() {
let interp = make_interpreter();
let result = interp.eval_integer_method(-10, "abs", &[]).unwrap();
assert_eq!(result, Value::Integer(10));
}
#[test]
fn test_eval_integer_method_positive_abs() {
let interp = make_interpreter();
let result = interp.eval_integer_method(10, "abs", &[]).unwrap();
assert_eq!(result, Value::Integer(10));
}
#[test]
fn test_eval_method_call_push_on_array() {
let mut interp = make_interpreter();
interp.set_variable("arr", Value::Array(Arc::from(vec![Value::Integer(1)])));
let receiver = make_expr(ExprKind::Identifier("arr".to_string()));
let arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(
2, None,
)));
let result = interp.eval_method_call(&receiver, "push", &[arg]).unwrap();
assert_eq!(result, Value::Nil);
let arr = interp.lookup_variable("arr").unwrap();
if let Value::Array(values) = arr {
assert_eq!(values.len(), 2);
assert_eq!(values[1], Value::Integer(2));
} else {
panic!("Expected Array");
}
}
#[test]
fn test_eval_method_call_pop_on_array() {
let mut interp = make_interpreter();
interp.set_variable(
"arr",
Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)])),
);
let receiver = make_expr(ExprKind::Identifier("arr".to_string()));
let result = interp.eval_method_call(&receiver, "pop", &[]).unwrap();
assert_eq!(result, Value::Integer(2));
let arr = interp.lookup_variable("arr").unwrap();
if let Value::Array(values) = arr {
assert_eq!(values.len(), 1);
} else {
panic!("Expected Array");
}
}
#[test]
fn test_eval_method_call_pop_empty_array() {
let mut interp = make_interpreter();
interp.set_variable("arr", Value::Array(Arc::from(vec![])));
let receiver = make_expr(ExprKind::Identifier("arr".to_string()));
let result = interp.eval_method_call(&receiver, "pop", &[]).unwrap();
assert_eq!(result, Value::Nil);
}
#[test]
fn test_eval_method_call_push_on_objectmut_field() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"items".to_string(),
Value::Array(Arc::from(vec![Value::Integer(1)])),
);
let obj_mut = Value::ObjectMut(Arc::new(Mutex::new(obj)));
interp.set_variable("self", obj_mut);
let object = Box::new(make_expr(ExprKind::Identifier("self".to_string())));
let receiver = make_expr(ExprKind::FieldAccess {
object,
field: "items".to_string(),
});
let arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(
2, None,
)));
let result = interp.eval_method_call(&receiver, "push", &[arg]).unwrap();
assert_eq!(result, Value::Nil);
}
#[test]
fn test_eval_method_call_module() {
use crate::frontend::ast::Literal;
use std::cell::RefCell;
use std::rc::Rc;
let mut interp = make_interpreter();
let mut module = HashMap::new();
module.insert(
"__type".to_string(),
Value::from_string("Module".to_string()),
);
let body = make_expr(ExprKind::Literal(Literal::Integer(42, None)));
let closure = Value::Closure {
params: vec![],
body: Arc::new(body),
env: Rc::new(RefCell::new(HashMap::new())),
};
module.insert("get_answer".to_string(), closure);
interp.set_variable("math", Value::Object(Arc::new(module)));
let receiver = make_expr(ExprKind::Identifier("math".to_string()));
let result = interp
.eval_method_call(&receiver, "get_answer", &[])
.unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_eval_method_call_module_not_found() {
let mut interp = make_interpreter();
let mut module = HashMap::new();
module.insert(
"__type".to_string(),
Value::from_string("Module".to_string()),
);
interp.set_variable("math", Value::Object(Arc::new(module)));
let receiver = make_expr(ExprKind::Identifier("math".to_string()));
let result = interp.eval_method_call(&receiver, "nonexistent", &[]);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("no function named"));
}
#[test]
fn test_dispatch_to_actor_object() {
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Counter".to_string()),
);
let obj = Value::Object(Arc::new(actor));
let result = interp
.dispatch_method_call(&obj, "stop", &[], true)
.unwrap();
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_dispatch_to_class_object() {
let mut interp = make_interpreter();
let mut class_obj = HashMap::new();
class_obj.insert(
"__class".to_string(),
Value::from_string("MyClass".to_string()),
);
let obj = Value::Object(Arc::new(class_obj));
let result = interp.dispatch_method_call(&obj, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_struct_object() {
let mut interp = make_interpreter();
let mut struct_obj = HashMap::new();
struct_obj.insert(
"__struct".to_string(),
Value::from_string("Point".to_string()),
);
let obj = Value::Object(Arc::new(struct_obj));
let result = interp.dispatch_method_call(&obj, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_struct_type_object() {
let mut interp = make_interpreter();
let mut struct_obj = HashMap::new();
struct_obj.insert(
"__struct_type".to_string(),
Value::from_string("Point".to_string()),
);
let obj = Value::Object(Arc::new(struct_obj));
let result = interp.dispatch_method_call(&obj, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_dataframe_builder() {
let mut interp = make_interpreter();
let mut builder = HashMap::new();
builder.insert(
"__type".to_string(),
Value::from_string("DataFrameBuilder".to_string()),
);
let obj = Value::Object(Arc::new(builder));
let result = interp.dispatch_method_call(&obj, "unknown", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_actor_objectmut() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Counter".to_string()),
);
let obj = Value::ObjectMut(Arc::new(Mutex::new(actor)));
let result = interp
.dispatch_method_call(&obj, "stop", &[], true)
.unwrap();
assert_eq!(result, Value::Bool(true));
}
#[test]
fn test_dispatch_to_class_objectmut() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut class_obj = HashMap::new();
class_obj.insert(
"__class".to_string(),
Value::from_string("MyClass".to_string()),
);
let obj = Value::ObjectMut(Arc::new(Mutex::new(class_obj)));
let result = interp.dispatch_method_call(&obj, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_struct_objectmut() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut struct_obj = HashMap::new();
struct_obj.insert(
"__struct".to_string(),
Value::from_string("Point".to_string()),
);
let obj = Value::ObjectMut(Arc::new(Mutex::new(struct_obj)));
let result = interp.dispatch_method_call(&obj, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_file_objectmut() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut file_obj = HashMap::new();
file_obj.insert("__type".to_string(), Value::from_string("File".to_string()));
let obj = Value::ObjectMut(Arc::new(Mutex::new(file_obj)));
let result = interp.dispatch_method_call(&obj, "close", &[], true);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_dispatch_to_generic_objectmut() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj_map = HashMap::new();
obj_map.insert(
"__type".to_string(),
Value::from_string("GenericType".to_string()),
);
let obj = Value::ObjectMut(Arc::new(Mutex::new(obj_map)));
let result = interp.dispatch_method_call(&obj, "test", &[], true);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unknown object type"));
}
#[test]
fn test_dispatch_to_value_struct() {
let mut interp = make_interpreter();
let fields: HashMap<String, Value> = HashMap::new();
let v = Value::Struct {
name: "Point".to_string(),
fields: Arc::new(fields),
};
let result = interp.dispatch_method_call(&v, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_value_class() {
use std::sync::RwLock;
let mut interp = make_interpreter();
let v = Value::Class {
class_name: "Person".to_string(),
fields: Arc::new(RwLock::new(HashMap::new())),
methods: Arc::new(HashMap::new()),
};
let result = interp.dispatch_method_call(&v, "unknown_method", &[], true);
assert!(result.is_err());
}
#[test]
fn test_dispatch_to_bool() {
let mut interp = make_interpreter();
let v = Value::Bool(true);
let result = interp.dispatch_method_call(&v, "to_string", &[], true);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_method_call_actor_send_undefined_message() {
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Counter".to_string()),
);
interp.set_variable("counter", Value::Object(Arc::new(actor)));
let receiver = make_expr(ExprKind::Identifier("counter".to_string()));
let arg = make_expr(ExprKind::Identifier("Increment".to_string()));
let result = interp.eval_method_call(&receiver, "send", &[arg]);
assert!(result.is_err());
}
#[test]
fn test_method_call_actor_ask_defined_variable() {
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Echo".to_string()),
);
interp.set_variable("echo", Value::Object(Arc::new(actor)));
interp.set_variable("msg", Value::Integer(42));
let receiver = make_expr(ExprKind::Identifier("echo".to_string()));
let arg = make_expr(ExprKind::Identifier("msg".to_string()));
let result = interp.eval_method_call(&receiver, "ask", &[arg]).unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_method_call_objectmut_actor_send() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Counter".to_string()),
);
interp.set_variable("counter", Value::ObjectMut(Arc::new(Mutex::new(actor))));
let receiver = make_expr(ExprKind::Identifier("counter".to_string()));
let arg = make_expr(ExprKind::Identifier("Increment".to_string()));
let result = interp.eval_method_call(&receiver, "send", &[arg]);
assert!(result.is_err());
}
#[test]
fn test_method_call_non_actor_send() {
let mut interp = make_interpreter();
let obj = HashMap::new();
interp.set_variable("obj", Value::Object(Arc::new(obj)));
let receiver = make_expr(ExprKind::Identifier("obj".to_string()));
let arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(
1, None,
)));
let result = interp.eval_method_call(&receiver, "send", &[arg]);
assert!(result.is_err());
}
#[test]
fn test_method_call_objectmut_identifier() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__type".to_string(),
Value::from_string("SomeType".to_string()),
);
interp.set_variable("obj", Value::ObjectMut(Arc::new(Mutex::new(obj))));
let receiver = make_expr(ExprKind::Identifier("obj".to_string()));
let result = interp.eval_method_call(&receiver, "test", &[]);
assert!(result.is_err());
}
#[test]
fn test_method_call_struct_identifier_with_impl() {
use crate::frontend::ast::Literal;
use std::cell::RefCell;
use std::rc::Rc;
let mut interp = make_interpreter();
let body = make_expr(ExprKind::Literal(Literal::Integer(99, None)));
let closure = Value::Closure {
params: vec![("self".to_string(), None)],
body: Arc::new(body),
env: Rc::new(RefCell::new(HashMap::new())),
};
interp.set_variable("Point::get_x", closure);
let fields: HashMap<String, Value> = HashMap::new();
let struct_val = Value::Struct {
name: "Point".to_string(),
fields: Arc::new(fields),
};
interp.set_variable("p", struct_val);
let receiver = make_expr(ExprKind::Identifier("p".to_string()));
let result = interp.eval_method_call(&receiver, "get_x", &[]).unwrap();
assert_eq!(result, Value::Integer(99));
}
#[test]
fn test_eval_method_call_push_on_array_identifier() {
use crate::frontend::ast::Literal;
let mut interp = make_interpreter();
interp.set_variable(
"items",
Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)])),
);
let receiver = make_expr(ExprKind::Identifier("items".to_string()));
let arg = make_expr(ExprKind::Literal(Literal::Integer(3, None)));
let result = interp
.eval_method_call(&receiver, "push", &[arg])
.unwrap();
assert_eq!(result, Value::Nil);
let arr = interp.lookup_variable("items").unwrap();
if let Value::Array(v) = arr {
assert_eq!(v.len(), 3);
assert_eq!(v[2], Value::Integer(3));
} else {
panic!("Expected Array");
}
}
#[test]
fn test_eval_method_call_pop_on_array_identifier() {
let mut interp = make_interpreter();
interp.set_variable(
"items",
Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2)])),
);
let receiver = make_expr(ExprKind::Identifier("items".to_string()));
let result = interp
.eval_method_call(&receiver, "pop", &[])
.unwrap();
assert_eq!(result, Value::Integer(2));
let arr = interp.lookup_variable("items").unwrap();
if let Value::Array(v) = arr {
assert_eq!(v.len(), 1);
} else {
panic!("Expected Array");
}
}
#[test]
fn test_eval_method_call_pop_on_empty_array() {
let mut interp = make_interpreter();
interp.set_variable("items", Value::Array(Arc::from(vec![])));
let receiver = make_expr(ExprKind::Identifier("items".to_string()));
let result = interp
.eval_method_call(&receiver, "pop", &[])
.unwrap();
assert_eq!(result, Value::Nil);
}
#[test]
fn test_eval_method_call_field_access_push() {
use crate::frontend::ast::Literal;
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"messages".to_string(),
Value::Array(Arc::from(vec![Value::Integer(1)])),
);
interp.set_variable("self_obj", Value::ObjectMut(Arc::new(Mutex::new(obj))));
let field_access = make_expr(ExprKind::FieldAccess {
object: Box::new(make_expr(ExprKind::Identifier("self_obj".to_string()))),
field: "messages".to_string(),
});
let arg = make_expr(ExprKind::Literal(Literal::Integer(2, None)));
let result = interp
.eval_method_call(&field_access, "push", &[arg])
.unwrap();
assert_eq!(result, Value::Nil);
}
#[test]
fn test_eval_method_call_stdlib_namespace() {
use crate::frontend::ast::Literal;
let mut interp = make_interpreter();
let receiver = make_expr(ExprKind::Identifier("Html".to_string()));
let arg = make_expr(ExprKind::Literal(Literal::String(
"<p>hello</p>".to_string(),
)));
let result = interp.eval_method_call(&receiver, "parse", &[arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_push_on_non_array() {
use crate::frontend::ast::Literal;
let mut interp = make_interpreter();
interp.set_variable("x", Value::Integer(42));
let receiver = make_expr(ExprKind::Identifier("x".to_string()));
let arg = make_expr(ExprKind::Literal(Literal::Integer(1, None)));
let result = interp.eval_method_call(&receiver, "push", &[arg]);
assert!(result.is_err());
}
#[test]
fn test_eval_method_call_pop_returns_last_item() {
let mut interp = make_interpreter();
interp.set_variable(
"items",
Value::Array(Arc::from(vec![
Value::Integer(10),
Value::Integer(20),
Value::Integer(30),
])),
);
let receiver = make_expr(ExprKind::Identifier("items".to_string()));
let result = interp
.eval_method_call(&receiver, "pop", &[])
.unwrap();
assert_eq!(result, Value::Integer(30));
let arr = interp.lookup_variable("items").unwrap();
if let Value::Array(v) = arr {
assert_eq!(v.len(), 2);
} else {
panic!("Expected Array");
}
}
#[test]
fn test_eval_method_call_module_method() {
use crate::frontend::ast::Literal;
let mut interp = make_interpreter();
let func_body = make_expr(ExprKind::Identifier("x".to_string()));
let func_val = Value::Closure {
params: vec![("x".to_string(), None)],
body: Arc::new(func_body),
env: std::rc::Rc::new(std::cell::RefCell::new(HashMap::new())),
};
let mut mod_obj = HashMap::new();
mod_obj.insert(
"__type".to_string(),
Value::from_string("Module".to_string()),
);
mod_obj.insert("my_func".to_string(), func_val);
interp.set_variable("mymod", Value::Object(Arc::new(mod_obj)));
let receiver = make_expr(ExprKind::Identifier("mymod".to_string()));
let arg = make_expr(ExprKind::Literal(Literal::Integer(42, None)));
let result = interp
.eval_method_call(&receiver, "my_func", &[arg])
.unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
fn test_eval_method_call_actor_send_message() {
let mut interp = make_interpreter();
let mut actor_obj = HashMap::new();
actor_obj.insert("__actor".to_string(), Value::Bool(true));
interp.set_variable("my_actor", Value::Object(Arc::new(actor_obj)));
let receiver = make_expr(ExprKind::Identifier("my_actor".to_string()));
let msg_arg = make_expr(ExprKind::Identifier("Ping".to_string()));
let result = interp.eval_method_call(&receiver, "send", &[msg_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_objectmut_binding_update() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"__type".to_string(),
Value::from_string("ObjectMut".to_string()),
);
obj.insert("x".to_string(), Value::Integer(1));
interp.set_variable("mutable_obj", Value::ObjectMut(Arc::new(Mutex::new(obj))));
let receiver = make_expr(ExprKind::Identifier("mutable_obj".to_string()));
let result = interp.eval_method_call(&receiver, "to_string", &[]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_array_pop_on_identifier() {
let mut interp = make_interpreter();
interp.set_variable(
"my_arr",
Value::Array(Arc::from(vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)])),
);
let receiver = make_expr(ExprKind::Identifier("my_arr".to_string()));
let result = interp.eval_method_call(&receiver, "pop", &[]);
assert!(result.is_ok(), "Array pop should succeed");
assert_eq!(result.unwrap(), Value::Integer(3));
}
#[test]
fn test_eval_method_call_array_push_on_identifier() {
let mut interp = make_interpreter();
interp.set_variable(
"my_arr",
Value::Array(Arc::from(vec![Value::Integer(1)])),
);
let receiver = make_expr(ExprKind::Identifier("my_arr".to_string()));
let push_arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(99, None)));
let result = interp.eval_method_call(&receiver, "push", &[push_arg]);
assert!(result.is_ok(), "Array push should succeed");
let updated = interp.lookup_variable("my_arr").unwrap();
if let Value::Array(arr) = updated {
assert_eq!(arr.len(), 2);
assert_eq!(arr[1], Value::Integer(99));
} else {
panic!("Expected array after push");
}
}
#[test]
fn test_eval_method_call_module_function() {
let mut interp = make_interpreter();
let mut module_obj = HashMap::new();
module_obj.insert(
"__type".to_string(),
Value::from_string("Module".to_string()),
);
module_obj.insert(
"__name".to_string(),
Value::from_string("math".to_string()),
);
module_obj.insert(
"double".to_string(),
Value::Closure {
params: vec![("x".to_string(), None)],
body: Arc::new(make_expr(ExprKind::Binary {
left: Box::new(make_expr(ExprKind::Identifier("x".to_string()))),
op: crate::frontend::ast::BinaryOp::Multiply,
right: Box::new(make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(2, None)))),
})),
env: std::rc::Rc::new(std::cell::RefCell::new(HashMap::new())),
},
);
interp.set_variable("math", Value::Object(Arc::new(module_obj)));
let receiver = make_expr(ExprKind::Identifier("math".to_string()));
let arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(5, None)));
let result = interp.eval_method_call(&receiver, "double", &[arg]);
assert!(result.is_ok(), "Module method call should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(10));
}
#[test]
fn test_eval_method_call_stdlib_namespace_coverage() {
let mut interp = make_interpreter();
let receiver = make_expr(ExprKind::Identifier("Math".to_string()));
let arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Float(-5.0)));
let result = interp.eval_method_call(&receiver, "abs", &[arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_objectmut_push_on_field() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut obj = HashMap::new();
obj.insert(
"items".to_string(),
Value::Array(Arc::from(vec![Value::Integer(1)])),
);
interp.set_variable("container", Value::ObjectMut(Arc::new(Mutex::new(obj))));
let object_expr = make_expr(ExprKind::Identifier("container".to_string()));
let receiver = make_expr(ExprKind::FieldAccess {
object: Box::new(object_expr),
field: "items".to_string(),
});
let push_arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(42, None)));
let result = interp.eval_method_call(&receiver, "push", &[push_arg]);
assert!(result.is_ok(), "ObjectMut field push should succeed: {:?}", result.err());
}
#[test]
fn test_eval_method_call_actor_ask() {
let mut interp = make_interpreter();
let mut actor_obj = HashMap::new();
actor_obj.insert("__actor".to_string(), Value::Bool(true));
interp.set_variable("my_actor", Value::Object(Arc::new(actor_obj)));
let receiver = make_expr(ExprKind::Identifier("my_actor".to_string()));
let msg_arg = make_expr(ExprKind::Identifier("Status".to_string()));
let result = interp.eval_method_call(&receiver, "ask", &[msg_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_via_eval_string() {
let mut interp = make_interpreter();
let result = interp.eval_string("\"hello\".len()");
assert!(result.is_ok(), "String method via eval_string: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(5));
}
#[test]
fn test_eval_method_call_float_method() {
let mut interp = make_interpreter();
let result = interp.eval_string("9.0.sqrt()");
assert!(result.is_ok(), "Float sqrt via eval_string: {:?}", result.err());
}
#[test]
fn test_eval_method_call_integer_method() {
let mut interp = make_interpreter();
let receiver = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(-42, None)));
let result = interp.eval_method_call(&receiver, "abs", &[]);
assert!(result.is_ok(), "Integer abs should succeed");
assert_eq!(result.unwrap(), Value::Integer(42));
}
#[test]
fn test_eval_method_call_dataframe_filter() {
use crate::frontend::ast::Literal;
use crate::runtime::interpreter::DataFrameColumn;
let mut interp = make_interpreter();
let df = Value::DataFrame {
columns: vec![
DataFrameColumn {
name: "x".to_string(),
values: vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)],
},
],
};
interp.set_variable("df", df);
let receiver = make_expr(ExprKind::Identifier("df".to_string()));
let closure_body = make_expr(ExprKind::Literal(Literal::Bool(true)));
let closure_arg = make_expr(ExprKind::Lambda {
params: vec![crate::frontend::ast::Param {
pattern: crate::frontend::ast::Pattern::Identifier("row".to_string()),
ty: crate::frontend::ast::Type {
kind: crate::frontend::ast::TypeKind::Named("Any".to_string()),
span: crate::frontend::ast::Span::default(),
},
span: crate::frontend::ast::Span::default(),
is_mutable: false,
default_value: None,
}],
body: Box::new(closure_body),
});
let result = interp.eval_method_call(&receiver, "filter", &[closure_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_dataframe_with_column() {
use crate::frontend::ast::Literal;
use crate::runtime::interpreter::DataFrameColumn;
let mut interp = make_interpreter();
let df = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "x".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
}],
};
interp.set_variable("df", df);
let receiver = make_expr(ExprKind::Identifier("df".to_string()));
let col_name = make_expr(ExprKind::Literal(Literal::String("y".to_string())));
let closure_body = make_expr(ExprKind::Literal(Literal::Integer(42, None)));
let closure_arg = make_expr(ExprKind::Lambda {
params: vec![crate::frontend::ast::Param {
pattern: crate::frontend::ast::Pattern::Identifier("row".to_string()),
ty: crate::frontend::ast::Type {
kind: crate::frontend::ast::TypeKind::Named("Any".to_string()),
span: crate::frontend::ast::Span::default(),
},
span: crate::frontend::ast::Span::default(),
is_mutable: false,
default_value: None,
}],
body: Box::new(closure_body),
});
let result = interp.eval_method_call(&receiver, "with_column", &[col_name, closure_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_dataframe_transform() {
use crate::frontend::ast::Literal;
use crate::runtime::interpreter::DataFrameColumn;
let mut interp = make_interpreter();
let df = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "x".to_string(),
values: vec![Value::Integer(1), Value::Integer(2)],
}],
};
interp.set_variable("df", df);
let receiver = make_expr(ExprKind::Identifier("df".to_string()));
let col_name = make_expr(ExprKind::Literal(Literal::String("x".to_string())));
let closure_body = make_expr(ExprKind::Identifier("val".to_string()));
let closure_arg = make_expr(ExprKind::Lambda {
params: vec![crate::frontend::ast::Param {
pattern: crate::frontend::ast::Pattern::Identifier("val".to_string()),
ty: crate::frontend::ast::Type {
kind: crate::frontend::ast::TypeKind::Named("Any".to_string()),
span: crate::frontend::ast::Span::default(),
},
span: crate::frontend::ast::Span::default(),
is_mutable: false,
default_value: None,
}],
body: Box::new(closure_body),
});
let result = interp.eval_method_call(&receiver, "transform", &[col_name, closure_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_dataframe_non_closure_method() {
use crate::runtime::interpreter::DataFrameColumn;
let mut interp = make_interpreter();
let df = Value::DataFrame {
columns: vec![DataFrameColumn {
name: "x".to_string(),
values: vec![Value::Integer(10), Value::Integer(20)],
}],
};
interp.set_variable("df", df);
let receiver = make_expr(ExprKind::Identifier("df".to_string()));
let result = interp.eval_method_call(&receiver, "sum", &[]);
assert!(result.is_ok(), "DataFrame sum should succeed: {:?}", result.err());
assert_eq!(result.unwrap(), Value::Integer(30));
}
#[test]
fn test_eval_method_call_objectmut_actor_send_undefined_msg() {
use std::sync::Mutex;
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Worker".to_string()),
);
interp.set_variable("worker", Value::ObjectMut(Arc::new(Mutex::new(actor))));
let receiver = make_expr(ExprKind::Identifier("worker".to_string()));
let msg_arg = make_expr(ExprKind::Identifier("DoWork".to_string()));
let result = interp.eval_method_call(&receiver, "send", &[msg_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_actor_send_with_expression_arg() {
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Echo".to_string()),
);
interp.set_variable("echo", Value::Object(Arc::new(actor)));
let receiver = make_expr(ExprKind::Identifier("echo".to_string()));
let msg_arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::Integer(99, None)));
let result = interp.eval_method_call(&receiver, "send", &[msg_arg]);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_eval_method_call_actor_ask_with_expression_arg() {
let mut interp = make_interpreter();
let mut actor = HashMap::new();
actor.insert(
"__actor".to_string(),
Value::from_string("Echo".to_string()),
);
interp.set_variable("echo", Value::Object(Arc::new(actor)));
let receiver = make_expr(ExprKind::Identifier("echo".to_string()));
let msg_arg = make_expr(ExprKind::Literal(crate::frontend::ast::Literal::String("hello".to_string())));
let result = interp.eval_method_call(&receiver, "ask", &[msg_arg]);
assert!(result.is_ok() || result.is_err());
}
}