use std::collections::BTreeMap;
use std::sync::Arc;
use tatara_lisp::{Atom, Span, Spanned, SpannedForm};
use tatara_lisp_eval::value::Closure;
use tatara_lisp_eval::vm::run::CompiledClosure;
use tatara_lisp_eval::Value;
use crate::mailbox::copy_data;
#[derive(Clone, Debug, Default)]
pub struct Code {
pub defs: Vec<Spanned>,
pub bindings: Vec<(Arc<str>, Value)>,
pub start: Option<Spanned>,
}
impl Code {
#[must_use]
pub fn program(&self) -> Vec<Spanned> {
let mut forms = self.defs.clone();
forms.extend(self.start.clone());
forms
}
pub fn bind(&mut self, name: &str, value: Value) {
match self.bindings.iter_mut().find(|(n, _)| &**n == name) {
Some(slot) => slot.1 = value,
None => self.bindings.push((Arc::from(name), value)),
}
}
}
#[derive(Clone, Debug)]
pub enum Entry {
Named(String),
Lambda(Spanned),
Builtin(String),
}
#[derive(Clone)]
enum Function {
Source(Arc<Closure>),
Builtin(Arc<str>),
}
fn function_of(v: &Value) -> Option<Function> {
match v {
Value::Closure(c) => Some(Function::Source(c.clone())),
Value::NativeFn(n) => Some(Function::Builtin(n.name.clone())),
Value::Foreign(any) => any.clone().downcast::<CompiledClosure>().ok().map(|cc| {
let lifted = cc.lift_to_closure();
Function::Source(Arc::new(Closure {
params: lifted.params.clone(),
rest: lifted.rest.clone(),
body: lifted.body.clone(),
captured_env: lifted.captured_env.clone(),
source: lifted.source,
name: cc.body.name.clone(),
}))
}),
_ => None,
}
}
#[must_use]
pub fn name_of(v: &Value) -> Option<String> {
match function_of(v)? {
Function::Source(c) => c.name.as_deref().map(str::to_string),
Function::Builtin(n) => Some(n.to_string()),
}
}
#[must_use]
pub fn is_function(v: &Value) -> bool {
function_of(v).is_some()
}
fn sym(name: &str) -> Spanned {
Spanned::new(
Span::synthetic(),
SpannedForm::Atom(Atom::Symbol(name.to_string())),
)
}
fn list(items: Vec<Spanned>) -> Spanned {
Spanned::new(Span::synthetic(), SpannedForm::List(items))
}
fn params_of(c: &Closure) -> Spanned {
let mut ps: Vec<Spanned> = c.params.iter().map(|p| sym(p)).collect();
if let Some(r) = &c.rest {
ps.push(sym("&rest"));
ps.push(sym(r));
}
list(ps)
}
fn lambda_of(c: &Closure) -> Spanned {
let mut items = vec![sym("lambda"), params_of(c)];
items.extend(c.body.iter().cloned());
Spanned::new(c.source, SpannedForm::List(items))
}
fn define_of(name: &str, c: &Closure) -> Spanned {
let mut head = vec![sym(name)];
if let SpannedForm::List(ps) = params_of(c).form {
head.extend(ps);
}
let mut items = vec![sym("define"), list(head)];
items.extend(c.body.iter().cloned());
Spanned::new(c.source, SpannedForm::List(items))
}
#[must_use]
pub fn define_value(name: &str, value: Spanned) -> Spanned {
list(vec![sym("define"), sym(name), value])
}
#[must_use]
pub fn call(head: Spanned, args: &[&str]) -> Spanned {
let mut items = vec![head];
items.extend(args.iter().map(|a| sym(a)));
list(items)
}
#[must_use]
pub fn symbol(name: &str) -> Spanned {
sym(name)
}
fn same(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Closure(x), Value::Closure(y)) => Arc::ptr_eq(x, y),
(Value::Foreign(x), Value::Foreign(y)) => Arc::ptr_eq(x, y),
(Value::List(x), Value::List(y)) => Arc::ptr_eq(x, y),
(Value::Map(x), Value::Map(y)) => Arc::ptr_eq(x, y),
(Value::NativeFn(x), Value::NativeFn(y)) => x.name == y.name,
_ => crate::pattern::data_eq(a, b),
}
}
struct Walk {
defs: Vec<Spanned>,
seen: BTreeMap<String, Value>,
bindings: Vec<(Arc<str>, Value)>,
}
impl Walk {
fn new() -> Self {
Self {
defs: Vec::new(),
seen: BTreeMap::new(),
bindings: Vec::new(),
}
}
fn reach(&mut self, c: &Closure) -> Result<(), String> {
let free = blue_lang_waku::free_names(&[lambda_of(c)], &|_| false);
for name in free {
let Some(v) = c.captured_env.lookup(&name) else {
continue;
};
self.bring(&name, &v)?;
}
Ok(())
}
fn bring(&mut self, name: &str, v: &Value) -> Result<(), String> {
if let Some(prev) = self.seen.get(name) {
if same(prev, v) {
return Ok(());
}
return Err(format!(
"spawn reaches two different values named `{name}`; rename one"
));
}
self.seen.insert(name.to_string(), v.clone());
match function_of(v) {
Some(Function::Builtin(_)) => Ok(()),
Some(Function::Source(k)) => {
self.defs.push(define_of(name, &k));
self.reach(&k)
}
None => {
let copy = copy_data(v).map_err(|kind| {
format!(
"spawn copies the values a process reaches, and `{name}` is a {kind}, \
which is not data; create it inside the process"
)
})?;
self.bindings.push((Arc::from(name), copy));
Ok(())
}
}
}
}
pub fn spawn_code(f: &Value, args: &[Value]) -> Result<(Code, Entry), String> {
let function =
function_of(f).ok_or_else(|| format!("spawn takes a function, got {}", f.type_name()))?;
let mut walk = Walk::new();
let mut arg_names = Vec::with_capacity(args.len());
for (i, a) in args.iter().enumerate() {
let name = format!("%a{i}");
let copy = copy_data(a).map_err(|kind| {
format!(
"spawn copies its arguments, and argument {} is a {kind}, which is not data",
i + 1
)
})?;
walk.bindings.push((Arc::from(name.as_str()), copy));
arg_names.push(name);
}
let names: Vec<&str> = arg_names.iter().map(String::as_str).collect();
let (entry, start) = match function {
Function::Builtin(n) => (Entry::Builtin(n.to_string()), call(sym(&n), &names)),
Function::Source(c) => {
let named = c.name.as_deref().filter(|n| {
c.captured_env
.lookup(n)
.is_some_and(|v| name_of(&v).as_deref() == Some(*n))
});
match named {
Some(n) => {
let global = c
.captured_env
.lookup(n)
.unwrap_or(Value::Closure(c.clone()));
walk.bring(n, &global)?;
(Entry::Named(n.to_string()), call(sym(n), &names))
}
None => {
walk.reach(&c)?;
let lambda = lambda_of(&c);
(Entry::Lambda(lambda.clone()), call(lambda, &names))
}
}
}
};
Ok((
Code {
defs: walk.defs,
bindings: walk.bindings,
start: Some(start),
},
entry,
))
}
pub fn function_code(f: &Value, as_name: &str) -> Result<Code, String> {
let function =
function_of(f).ok_or_else(|| format!("expected a function, got {}", f.type_name()))?;
let mut walk = Walk::new();
match function {
Function::Builtin(n) => {
walk.defs.push(define_value(as_name, sym(&n)));
}
Function::Source(c) => {
let named = c.name.as_deref().filter(|n| {
c.captured_env
.lookup(n)
.is_some_and(|v| name_of(&v).as_deref() == Some(*n))
});
match named {
Some(n) => {
let global = c
.captured_env
.lookup(n)
.unwrap_or(Value::Closure(c.clone()));
walk.bring(n, &global)?;
if n != as_name {
walk.defs.push(define_value(as_name, sym(n)));
}
}
None => {
walk.reach(&c)?;
walk.defs.push(define_value(as_name, lambda_of(&c)));
}
}
}
}
Ok(Code {
defs: walk.defs,
bindings: walk.bindings,
start: None,
})
}