Skip to main content

blue_lang_proc/
code.rs

1//! What a new process runs: a function value turned back into code.
2//!
3//! `spawn(f, args)` hands the new process **the code of `f` and copies of the
4//! values it reaches**, never a reference into the spawner's environment. That
5//! is Erlang's rule for a fun sent to `spawn`, and it is the only reading of
6//! "processes share nothing" that survives a function value: a closure carries
7//! its defining environment, and sharing that environment is the coupling
8//! process isolation exists to remove (`mailbox::deep_copy` refuses closures
9//! for the same reason).
10//!
11//! So the spawner's function is walked, not shared:
12//!
13//! - every free name it reaches that is bound to **a function** becomes a
14//!   definition in the child, recursively, so helpers come along;
15//! - every free name bound to **data** becomes a binding in the child, a deep
16//!   copy taken now (a local the closure captured, a top-level constant);
17//! - a name bound to a builtin is left alone: the child has its own runtime;
18//! - a name bound to anything else (a host handle, a promise) is refused by
19//!   name, because a process must open its own resources.
20//!
21//! The result is compiled for the bytecode VM, because the VM is the executor
22//! that can preempt and park; a function the tree-walker defined is
23//! recompiled, not called across.
24
25use std::collections::BTreeMap;
26use std::sync::Arc;
27
28use tatara_lisp::{Atom, Span, Spanned, SpannedForm};
29use tatara_lisp_eval::value::Closure;
30use tatara_lisp_eval::vm::run::CompiledClosure;
31use tatara_lisp_eval::Value;
32
33use crate::mailbox::copy_data;
34
35/// A process's program: definitions, data bindings, and the form it starts at.
36///
37/// Kept as trees, not as a compiled chunk, because a restart, a migration and
38/// a crash trace all need the program again: the restart recompiles it, the
39/// migration extends it, and the trace writes it down so `blue replay` needs
40/// nothing but the trace.
41#[derive(Clone, Debug, Default)]
42pub struct Code {
43    /// `(define (name params…) body…)` forms, one per function reached.
44    pub defs: Vec<Spanned>,
45    /// Globals the child starts with: copied arguments and captured data.
46    pub bindings: Vec<(Arc<str>, Value)>,
47    /// The form the process evaluates; its value is the process's result.
48    pub start: Option<Spanned>,
49}
50
51impl Code {
52    /// Every form the VM compiles, in order.
53    #[must_use]
54    pub fn program(&self) -> Vec<Spanned> {
55        let mut forms = self.defs.clone();
56        forms.extend(self.start.clone());
57        forms
58    }
59
60    /// Set a binding, replacing an earlier one of the same name.
61    pub fn bind(&mut self, name: &str, value: Value) {
62        match self.bindings.iter_mut().find(|(n, _)| &**n == name) {
63            Some(slot) => slot.1 = value,
64            None => self.bindings.push((Arc::from(name), value)),
65        }
66    }
67}
68
69/// How the code reached its entry function.
70#[derive(Clone, Debug)]
71pub enum Entry {
72    /// A named function; the code defines it under this name.
73    Named(String),
74    /// An anonymous function, as a `(lambda …)` form.
75    Lambda(Spanned),
76    /// A builtin, called by name.
77    Builtin(String),
78}
79
80/// A function value normalised to its source shape.
81#[derive(Clone)]
82enum Function {
83    Source(Arc<Closure>),
84    Builtin(Arc<str>),
85}
86
87fn function_of(v: &Value) -> Option<Function> {
88    match v {
89        Value::Closure(c) => Some(Function::Source(c.clone())),
90        Value::NativeFn(n) => Some(Function::Builtin(n.name.clone())),
91        Value::Foreign(any) => any.clone().downcast::<CompiledClosure>().ok().map(|cc| {
92            // A VM closure keeps its source body; lifting gives it the same
93            // shape a tree-walker closure has, captures included.
94            let lifted = cc.lift_to_closure();
95            Function::Source(Arc::new(Closure {
96                params: lifted.params.clone(),
97                rest: lifted.rest.clone(),
98                body: lifted.body.clone(),
99                captured_env: lifted.captured_env.clone(),
100                source: lifted.source,
101                name: cc.body.name.clone(),
102            }))
103        }),
104        _ => None,
105    }
106}
107
108/// The name a function value was defined under, if it has one. What
109/// `defmigrate` matches running processes by.
110#[must_use]
111pub fn name_of(v: &Value) -> Option<String> {
112    match function_of(v)? {
113        Function::Source(c) => c.name.as_deref().map(str::to_string),
114        Function::Builtin(n) => Some(n.to_string()),
115    }
116}
117
118/// Is this value something `spawn` can run?
119#[must_use]
120pub fn is_function(v: &Value) -> bool {
121    function_of(v).is_some()
122}
123
124fn sym(name: &str) -> Spanned {
125    Spanned::new(
126        Span::synthetic(),
127        SpannedForm::Atom(Atom::Symbol(name.to_string())),
128    )
129}
130
131fn list(items: Vec<Spanned>) -> Spanned {
132    Spanned::new(Span::synthetic(), SpannedForm::List(items))
133}
134
135/// `(p1 p2 &rest r)`.
136fn params_of(c: &Closure) -> Spanned {
137    let mut ps: Vec<Spanned> = c.params.iter().map(|p| sym(p)).collect();
138    if let Some(r) = &c.rest {
139        ps.push(sym("&rest"));
140        ps.push(sym(r));
141    }
142    list(ps)
143}
144
145/// `(lambda (params…) body…)`, keeping the body's own spans.
146fn lambda_of(c: &Closure) -> Spanned {
147    let mut items = vec![sym("lambda"), params_of(c)];
148    items.extend(c.body.iter().cloned());
149    Spanned::new(c.source, SpannedForm::List(items))
150}
151
152/// `(define (name params…) body…)`.
153fn define_of(name: &str, c: &Closure) -> Spanned {
154    let mut head = vec![sym(name)];
155    if let SpannedForm::List(ps) = params_of(c).form {
156        head.extend(ps);
157    }
158    let mut items = vec![sym("define"), list(head)];
159    items.extend(c.body.iter().cloned());
160    Spanned::new(c.source, SpannedForm::List(items))
161}
162
163/// `(define name value-form)`.
164#[must_use]
165pub fn define_value(name: &str, value: Spanned) -> Spanned {
166    list(vec![sym("define"), sym(name), value])
167}
168
169/// `(f a1 a2 …)` where each argument is a global holding a copied value.
170#[must_use]
171pub fn call(head: Spanned, args: &[&str]) -> Spanned {
172    let mut items = vec![head];
173    items.extend(args.iter().map(|a| sym(a)));
174    list(items)
175}
176
177/// A symbol form.
178#[must_use]
179pub fn symbol(name: &str) -> Spanned {
180    sym(name)
181}
182
183/// Two `Value`s are the same binding when they are the same allocation.
184fn same(a: &Value, b: &Value) -> bool {
185    match (a, b) {
186        (Value::Closure(x), Value::Closure(y)) => Arc::ptr_eq(x, y),
187        (Value::Foreign(x), Value::Foreign(y)) => Arc::ptr_eq(x, y),
188        (Value::List(x), Value::List(y)) => Arc::ptr_eq(x, y),
189        (Value::Map(x), Value::Map(y)) => Arc::ptr_eq(x, y),
190        (Value::NativeFn(x), Value::NativeFn(y)) => x.name == y.name,
191        _ => crate::pattern::data_eq(a, b),
192    }
193}
194
195/// Collects the definitions and bindings one entry function reaches.
196struct Walk {
197    defs: Vec<Spanned>,
198    /// name → the value bound under it, so two different values reached
199    /// under one name are refused instead of one silently winning.
200    seen: BTreeMap<String, Value>,
201    bindings: Vec<(Arc<str>, Value)>,
202}
203
204impl Walk {
205    fn new() -> Self {
206        Self {
207            defs: Vec::new(),
208            seen: BTreeMap::new(),
209            bindings: Vec::new(),
210        }
211    }
212
213    /// Bring along everything `c`'s body names.
214    fn reach(&mut self, c: &Closure) -> Result<(), String> {
215        let free = blue_lang_waku::free_names(&[lambda_of(c)], &|_| false);
216        for name in free {
217            let Some(v) = c.captured_env.lookup(&name) else {
218                // A special form, a macro, or a global not defined yet: the
219                // child's runtime answers it or the call fails there.
220                continue;
221            };
222            self.bring(&name, &v)?;
223        }
224        Ok(())
225    }
226
227    fn bring(&mut self, name: &str, v: &Value) -> Result<(), String> {
228        if let Some(prev) = self.seen.get(name) {
229            if same(prev, v) {
230                return Ok(());
231            }
232            return Err(format!(
233                "spawn reaches two different values named `{name}`; rename one"
234            ));
235        }
236        self.seen.insert(name.to_string(), v.clone());
237        match function_of(v) {
238            // The child's own runtime binds every builtin, including its own
239            // process words, which must not be the spawner's.
240            Some(Function::Builtin(_)) => Ok(()),
241            Some(Function::Source(k)) => {
242                self.defs.push(define_of(name, &k));
243                self.reach(&k)
244            }
245            None => {
246                let copy = copy_data(v).map_err(|kind| {
247                    format!(
248                        "spawn copies the values a process reaches, and `{name}` is a {kind}, \
249                         which is not data; create it inside the process"
250                    )
251                })?;
252                self.bindings.push((Arc::from(name), copy));
253                Ok(())
254            }
255        }
256    }
257}
258
259/// The code that runs `f(args…)` in a new process.
260///
261/// # Errors
262///
263/// When `f` is not a function, or reaches a value that cannot be copied.
264pub fn spawn_code(f: &Value, args: &[Value]) -> Result<(Code, Entry), String> {
265    let function =
266        function_of(f).ok_or_else(|| format!("spawn takes a function, got {}", f.type_name()))?;
267    let mut walk = Walk::new();
268    let mut arg_names = Vec::with_capacity(args.len());
269    for (i, a) in args.iter().enumerate() {
270        let name = format!("%a{i}");
271        let copy = copy_data(a).map_err(|kind| {
272            format!(
273                "spawn copies its arguments, and argument {} is a {kind}, which is not data",
274                i + 1
275            )
276        })?;
277        walk.bindings.push((Arc::from(name.as_str()), copy));
278        arg_names.push(name);
279    }
280    let names: Vec<&str> = arg_names.iter().map(String::as_str).collect();
281    let (entry, start) = match function {
282        Function::Builtin(n) => (Entry::Builtin(n.to_string()), call(sym(&n), &names)),
283        Function::Source(c) => {
284            // A named function the spawner can still see under its name is a
285            // definition; anything else is called as the lambda it is.
286            let named = c.name.as_deref().filter(|n| {
287                c.captured_env
288                    .lookup(n)
289                    .is_some_and(|v| name_of(&v).as_deref() == Some(*n))
290            });
291            match named {
292                Some(n) => {
293                    let global = c
294                        .captured_env
295                        .lookup(n)
296                        .unwrap_or(Value::Closure(c.clone()));
297                    walk.bring(n, &global)?;
298                    (Entry::Named(n.to_string()), call(sym(n), &names))
299                }
300                None => {
301                    walk.reach(&c)?;
302                    let lambda = lambda_of(&c);
303                    (Entry::Lambda(lambda.clone()), call(lambda, &names))
304                }
305            }
306        }
307    };
308    Ok((
309        Code {
310            defs: walk.defs,
311            bindings: walk.bindings,
312            start: Some(start),
313        },
314        entry,
315    ))
316}
317
318/// The definitions and bindings `f` reaches, with `f` itself defined under
319/// `as_name`. What a migration installs and a server's handler starts from.
320///
321/// # Errors
322///
323/// As [`spawn_code`].
324pub fn function_code(f: &Value, as_name: &str) -> Result<Code, String> {
325    let function =
326        function_of(f).ok_or_else(|| format!("expected a function, got {}", f.type_name()))?;
327    let mut walk = Walk::new();
328    match function {
329        Function::Builtin(n) => {
330            walk.defs.push(define_value(as_name, sym(&n)));
331        }
332        Function::Source(c) => {
333            let named = c.name.as_deref().filter(|n| {
334                c.captured_env
335                    .lookup(n)
336                    .is_some_and(|v| name_of(&v).as_deref() == Some(*n))
337            });
338            match named {
339                Some(n) => {
340                    let global = c
341                        .captured_env
342                        .lookup(n)
343                        .unwrap_or(Value::Closure(c.clone()));
344                    walk.bring(n, &global)?;
345                    if n != as_name {
346                        walk.defs.push(define_value(as_name, sym(n)));
347                    }
348                }
349                None => {
350                    walk.reach(&c)?;
351                    walk.defs.push(define_value(as_name, lambda_of(&c)));
352                }
353            }
354        }
355    }
356    Ok(Code {
357        defs: walk.defs,
358        bindings: walk.bindings,
359        start: None,
360    })
361}