use std::cell::RefCell;
use rayforce_sys as sys;
use crate::error::{RayError, Result};
use crate::expr::{Expr, IntoExpr};
use crate::ops::Operation;
use crate::runtime::{eval, eval_value, get_global, set_global};
use crate::value::Value;
thread_local! {
static BIND_COUNTER: RefCell<u64> = const { RefCell::new(1) };
}
pub struct Fn {
value: Value,
source: Option<String>,
bound_name: RefCell<Option<String>>,
}
impl Fn {
pub fn new(source: &str) -> Result<Fn> {
if !source.trim_start().starts_with("(fn") {
return Err(RayError::binding(
"Fn::new: source must be a `(fn …)` expression",
));
}
let mut f = Fn::from_value(eval(source)?)?;
f.source = Some(source.to_string());
Ok(f)
}
pub fn from_global(name: &str) -> Result<Fn> {
let f = Fn::from_value(get_global(name)?)?;
*f.bound_name.borrow_mut() = Some(name.to_string());
Ok(f)
}
pub fn from_value(value: Value) -> Result<Fn> {
if value.type_code() != sys::RAY_LAMBDA as i8 {
return Err(RayError::binding(format!(
"Fn::from_value: expected a lambda, got type {}",
value.type_code()
)));
}
Ok(Fn {
value,
source: None,
bound_name: RefCell::new(None),
})
}
#[inline]
pub fn as_value(&self) -> &Value {
&self.value
}
pub fn call(&self, args: &[Value]) -> Result<Value> {
let mut items = Vec::with_capacity(args.len() + 1);
items.push(self.value.clone());
items.extend(args.iter().cloned());
eval_value(&Value::list(&items))
}
fn bind(&self) -> Result<String> {
if let Some(name) = self.bound_name.borrow().as_ref() {
return Ok(name.clone());
}
let n = BIND_COUNTER.with(|c| {
let mut c = c.borrow_mut();
let n = *c;
*c += 1;
n
});
let name = format!("__rustfn_{n:x}");
set_global(&name, &self.value)?;
*self.bound_name.borrow_mut() = Some(name.clone());
Ok(name)
}
pub fn apply<I>(&self, args: I) -> Result<Expr>
where
I: IntoIterator,
I::Item: IntoExpr,
{
let name = self.bind()?;
let operands = args.into_iter().map(IntoExpr::into_expr).collect();
Ok(Expr::Call(name, operands))
}
#[inline]
pub fn source(&self) -> Option<&str> {
self.source.as_deref()
}
pub fn meta(&self) -> Result<Value> {
let ast = Value::list(&[
Value::name_ref(Operation::Meta.as_str()),
self.value.clone(),
]);
eval_value(&ast)
}
}
impl Clone for Fn {
fn clone(&self) -> Fn {
Fn {
value: self.value.clone(),
source: self.source.clone(),
bound_name: RefCell::new(self.bound_name.borrow().clone()),
}
}
}
impl std::fmt::Display for Fn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.source {
Some(src) => f.write_str(src),
None => f.write_str(&self.value.format()),
}
}
}
impl std::fmt::Debug for Fn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Fn({self})")
}
}