use crate::diagnostics::{Error as DiagnosticError, Result};
use crate::effects::{Effect, MonadicValue};
use crate::eval::value::{Value, PrimitiveProcedure, PrimitiveImpl, ThreadSafeEnvironment};
use std::sync::Arc;
pub fn create_effect_bindings(env: &Arc<ThreadSafeEnvironment>) {
bind_monadic_functions(env);
bind_effect_handler_functions(env);
bind_io_functions(env);
bind_state_functions(env);
bind_error_functions(env);
bind_utility_functions(env);
}
fn bind_monadic_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("return".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "return".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_return),
effects: vec![Effect::Pure],
})));
env.define(">>=".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: ">>=".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_bind),
effects: vec![Effect::Pure],
})));
env.define(">>".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: ">>".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_sequence),
effects: vec![Effect::Pure],
})));
env.define("fmap".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "fmap".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_fmap),
effects: vec![Effect::Pure],
})));
env.define("join".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "join".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_join),
effects: vec![Effect::Pure],
})));
env.define("lift2".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "lift2".to_string(),
arity_min: 3,
arity_max: Some(3),
implementation: PrimitiveImpl::RustFn(primitive_lift2),
effects: vec![Effect::Pure],
})));
}
fn bind_effect_handler_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("with-handler".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "with-handler".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_with_handler),
effects: vec![Effect::Pure], })));
env.define("define-effect-handler".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "define-effect-handler".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_define_effect_handler),
effects: vec![Effect::State], })));
env.define("handle".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "handle".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_handle),
effects: vec![Effect::Pure],
})));
}
fn bind_io_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("io-return".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "io-return".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_io_return),
effects: vec![Effect::IO],
})));
env.define("io-bind".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "io-bind".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_io_bind),
effects: vec![Effect::IO],
})));
env.define("run-io".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "run-io".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_run_io),
effects: vec![Effect::IO],
})));
}
fn bind_state_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("state-return".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "state-return".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_state_return),
effects: vec![Effect::State],
})));
env.define("get-state".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-state".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_get_state),
effects: vec![Effect::State],
})));
env.define("put-state".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "put-state".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_put_state),
effects: vec![Effect::State],
})));
env.define("modify-state".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "modify-state".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_modify_state),
effects: vec![Effect::State],
})));
env.define("run-state".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "run-state".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_run_state),
effects: vec![Effect::State],
})));
}
fn bind_error_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("error-return".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "error-return".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_error_return),
effects: vec![Effect::Error],
})));
env.define("throw-error".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "throw-error".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_throw_error),
effects: vec![Effect::Error],
})));
env.define("catch-error".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "catch-error".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_catch_error),
effects: vec![Effect::Error],
})));
env.define("run-error".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "run-error".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_run_error),
effects: vec![Effect::Error],
})));
}
fn bind_utility_functions(env: &Arc<ThreadSafeEnvironment>) {
env.define("effect-pure?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "effect-pure?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_effect_pure_p),
effects: vec![Effect::Pure],
})));
env.define("get-effects".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-effects".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_get_effects),
effects: vec![Effect::Pure],
})));
env.define("lift-effect".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "lift-effect".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_lift_effect),
effects: vec![Effect::Pure],
})));
}
fn primitive_return(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("return expects 1 argument, got {}", args.len()),
None,
)));
}
let _monadic_val = MonadicValue::pure(args[0].clone());
Ok(Value::string(format!("Monadic({})", args[0])))
}
fn primitive_bind(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!(">>= expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Bind({}, {})", args[0], args[1])))
}
fn primitive_sequence(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!(">> expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Sequence({}, {})", args[0], args[1])))
}
fn primitive_fmap(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("fmap expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Fmap({}, {})", args[0], args[1])))
}
fn primitive_join(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("join expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Join({})", args[0])))
}
fn primitive_lift2(args: &[Value]) -> Result<Value> {
if args.len() != 3 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("lift2 expects 3 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Lift2({}, {}, {})", args[0], args[1], args[2])))
}
fn primitive_with_handler(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("with-handler expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("WithHandler({}, {})", args[0], args[1])))
}
fn primitive_define_effect_handler(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("define-effect-handler expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::Unspecified)
}
fn primitive_handle(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("handle expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("Handle({}, {})", args[0], args[1])))
}
fn primitive_io_return(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("io-return expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("IO({})", args[0])))
}
fn primitive_io_bind(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("io-bind expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("IOBind({}, {})", args[0], args[1])))
}
fn primitive_run_io(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("run-io expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(args[0].clone())
}
fn primitive_state_return(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("state-return expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("State({})", args[0])))
}
fn primitive_get_state(args: &[Value]) -> Result<Value> {
if !args.is_empty() {
return Err(Box::new(DiagnosticError::runtime_error(
format!("get-state expects 0 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string("GetState".to_string()))
}
fn primitive_put_state(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("put-state expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("PutState({})", args[0])))
}
fn primitive_modify_state(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("modify-state expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("ModifyState({})", args[0])))
}
fn primitive_run_state(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("run-state expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("RunState({}, {})", args[0], args[1])))
}
fn primitive_error_return(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("error-return expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("ErrorReturn({})", args[0])))
}
fn primitive_throw_error(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("throw-error expects 1 argument, got {}", args.len()),
None,
)));
}
Err(Box::new(DiagnosticError::runtime_error(
format!("Thrown error: {}", args[0]),
None,
)))
}
fn primitive_catch_error(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("catch-error expects 2 arguments, got {}", args.len()),
None,
)));
}
Ok(Value::string(format!("CatchError({}, {})", args[0], args[1])))
}
fn primitive_run_error(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("run-error expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(args[0].clone())
}
fn primitive_effect_pure_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("effect-pure? expects 1 argument, got {}", args.len()),
None,
)));
}
let is_pure = !args[0].as_string().map(|s| s.contains("IO") || s.contains("State") || s.contains("Error"))
.unwrap_or(false);
Ok(Value::boolean(is_pure))
}
fn primitive_get_effects(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("get-effects expects 1 argument, got {}", args.len()),
None,
)));
}
let effects = if let Some(s) = args[0].as_string() {
if s.contains("IO") {
vec![Value::string("IO".to_string())]
} else if s.contains("State") {
vec![Value::string("State".to_string())]
} else if s.contains("Error") {
vec![Value::string("Error".to_string())]
} else {
vec![Value::string("Pure".to_string())]
}
} else {
vec![Value::string("Pure".to_string())]
};
Ok(Value::list(effects))
}
fn primitive_lift_effect(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("lift-effect expects 2 arguments, got {}", args.len()),
None,
)));
}
let effect_name = args[0].as_string().unwrap_or("Unknown");
let value = &args[1];
Ok(Value::string(format!("{effect_name}({value})")))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_monadic_return() {
let args = vec![Value::integer(42)];
let result = primitive_return(&args).unwrap();
assert!(result.as_string().unwrap().contains("42"));
}
#[test]
fn test_effect_pure_check() {
let pure_val = vec![Value::integer(42)];
let result = primitive_effect_pure_p(&pure_val).unwrap();
assert_eq!(result, Value::boolean(true));
let io_val = vec![Value::string("IO(something)".to_string())];
let result = primitive_effect_pure_p(&io_val).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_error_throwing() {
let args = vec![Value::string("Test error".to_string())];
let result = primitive_throw_error(&args);
assert!(result.is_err());
}
}