Skip to main content

candela_vm/
embed.rs

1//! Host/script value marshalling for candela's embedding API.
2//!
3//! These types cross the boundary between a Rust host and a running candela
4//! script: [`Value`] is the dynamically-typed carrier, [`HostType`] describes
5//! the shapes that can cross, the `FromHostValue`/`IntoHostValue`/
6//! `IntoHostResult`/`IntoHostFn` traits adapt Rust closures into registered
7//! host functions, [`HostError`] is how one of them fails, [`HostRegistry`]
8//! holds the closures a script's `host` blocks bind to, and
9//! [`marshal_value`]/[`unmarshal_value`] convert between [`Value`] and the VM's
10//! NaN-boxed [`Data`]. They carry no compiler state and the VM depends on them,
11//! so they live in the VM-only crate. The `Engine`/`Program` embedding surface
12//! (which compiles and drives scripts) lives in the `candela` crate on top.
13
14use crate::data::Data;
15use crate::data::NULL;
16use crate::gc::GcState;
17use crate::gc::Heap;
18use crate::rt::DataType;
19use crate::rt::EnumType;
20use crate::rt::HostFnSig;
21use crate::rt::Pools;
22use crate::rt::Struct;
23use crate::vm::MapPool;
24use crate::vm::ObjectPool;
25use crate::vm::RegisterFile;
26use crate::vm::StringPool;
27use crate::vm::map_insert;
28use std::collections::BTreeMap;
29use std::collections::HashMap;
30use std::fmt;
31use std::rc::Rc;
32
33/// A dynamically-typed value passed across the host/script boundary.
34///
35/// [`Value::Int`] is an `i64`, the width a candela `int` holds, so a value
36/// crosses the boundary unchanged.
37#[derive(Debug, Clone, PartialEq)]
38pub enum Value {
39    Null,
40    Int(i64),
41    Float(f64),
42    Bool(bool),
43    String(String),
44    /// A candela array `T[]`. Elements are expected to be homogeneous, matching
45    /// candela's static array typing.
46    Array(Vec<Self>),
47    /// A candela string-keyed map `{string: V}` (or a struct read back as a record).
48    /// Ordered so equality and iteration are deterministic on the host side.
49    Map(BTreeMap<String, Self>),
50    /// A candela enum value: the variant it was built as, and that variant's
51    /// payload in declaration order. A nullary variant carries an empty
52    /// payload. The enum's own type name is not carried; a host reads the
53    /// variant, which is what it matches on.
54    Enum {
55        variant: String,
56        payload: Vec<Self>,
57    },
58}
59
60impl Value {
61    #[must_use]
62    pub const fn as_i64(&self) -> Option<i64> {
63        if let Self::Int(i) = self {
64            Some(*i)
65        } else {
66            None
67        }
68    }
69    #[must_use]
70    pub const fn as_f64(&self) -> Option<f64> {
71        if let Self::Float(f) = self {
72            Some(*f)
73        } else {
74            None
75        }
76    }
77    #[must_use]
78    pub const fn as_bool(&self) -> Option<bool> {
79        if let Self::Bool(b) = self {
80            Some(*b)
81        } else {
82            None
83        }
84    }
85    #[must_use]
86    pub const fn as_str(&self) -> Option<&str> {
87        if let Self::String(s) = self {
88            Some(s.as_str())
89        } else {
90            None
91        }
92    }
93    #[must_use]
94    pub const fn as_array(&self) -> Option<&Vec<Self>> {
95        if let Self::Array(a) = self {
96            Some(a)
97        } else {
98            None
99        }
100    }
101    #[must_use]
102    pub const fn as_map(&self) -> Option<&BTreeMap<String, Self>> {
103        if let Self::Map(m) = self {
104            Some(m)
105        } else {
106            None
107        }
108    }
109    #[must_use]
110    pub fn into_string(self) -> Option<String> {
111        if let Self::String(s) = self {
112            Some(s)
113        } else {
114            None
115        }
116    }
117    #[must_use]
118    pub fn into_array(self) -> Option<Vec<Self>> {
119        if let Self::Array(a) = self {
120            Some(a)
121        } else {
122            None
123        }
124    }
125    #[must_use]
126    pub fn into_map(self) -> Option<BTreeMap<String, Self>> {
127        if let Self::Map(m) = self {
128            Some(m)
129        } else {
130            None
131        }
132    }
133    #[must_use]
134    pub const fn is_null(&self) -> bool {
135        matches!(self, Self::Null)
136    }
137}
138
139impl From<i64> for Value {
140    fn from(v: i64) -> Self {
141        Self::Int(v)
142    }
143}
144impl From<i32> for Value {
145    fn from(v: i32) -> Self {
146        Self::Int(i64::from(v))
147    }
148}
149impl From<f64> for Value {
150    fn from(v: f64) -> Self {
151        Self::Float(v)
152    }
153}
154impl From<bool> for Value {
155    fn from(v: bool) -> Self {
156        Self::Bool(v)
157    }
158}
159impl From<String> for Value {
160    fn from(v: String) -> Self {
161        Self::String(v)
162    }
163}
164impl From<&str> for Value {
165    fn from(v: &str) -> Self {
166        Self::String(v.to_owned())
167    }
168}
169impl From<()> for Value {
170    fn from((): ()) -> Self {
171        Self::Null
172    }
173}
174impl<T: Into<Self>> From<Vec<T>> for Value {
175    fn from(v: Vec<T>) -> Self {
176        Self::Array(v.into_iter().map(Into::into).collect())
177    }
178}
179impl<T: Into<Self>> From<BTreeMap<String, T>> for Value {
180    fn from(m: BTreeMap<String, T>) -> Self {
181        Self::Map(m.into_iter().map(|(k, v)| (k, v.into())).collect())
182    }
183}
184impl<T: Into<Self>> From<HashMap<String, T>> for Value {
185    fn from(m: HashMap<String, T>) -> Self {
186        Self::Map(m.into_iter().map(|(k, v)| (k, v.into())).collect())
187    }
188}
189
190/// The type kinds that can cross the host boundary.
191///
192/// Used to type-check a registered closure against its `host` block declaration.
193/// `Array`/`Map` are recursive so nested shapes (e.g. `{string: string}[]`)
194/// validate structurally; `Map` is always string-keyed, so only the value type
195/// is carried.
196#[derive(Debug, Clone, PartialEq, Eq)]
197pub enum HostType {
198    Int,
199    Float,
200    Bool,
201    String,
202    Unit,
203    Array(Box<Self>),
204    Map(Box<Self>),
205    /// A struct a `host` block declares, as its fields in declaration order.
206    /// It crosses into the host only, as a [`Value::Map`] keyed by field
207    /// name; a host function cannot return one.
208    Struct(Vec<(String, Self)>),
209}
210
211impl HostType {
212    /// Maps a candela [`DataType`] onto the host type kind it marshals as, or
213    /// `None` for a type that cannot cross the boundary. `structs` is the
214    /// program's struct table, which a struct type is read from.
215    #[must_use]
216    pub fn from_datatype(dt: &DataType, structs: &[Struct]) -> Option<Self> {
217        Self::from_datatype_in(dt, structs, &mut Vec::new())
218    }
219
220    /// [`Self::from_datatype`], with the structs being converted on the way
221    /// down: a struct that holds itself has no finite host shape.
222    fn from_datatype_in(dt: &DataType, structs: &[Struct], open: &mut Vec<u16>) -> Option<Self> {
223        match dt {
224            DataType::Int => Some(Self::Int),
225            DataType::Float => Some(Self::Float),
226            DataType::Bool => Some(Self::Bool),
227            DataType::String => Some(Self::String),
228            DataType::Null => Some(Self::Unit),
229            DataType::Array(Some(inner)) => Some(Self::Array(Box::new(Self::from_datatype_in(
230                inner, structs, open,
231            )?))),
232            DataType::Map(kv) => {
233                // Only string-keyed maps cross the boundary.
234                match &kv.0 {
235                    Some(DataType::String) | None => {}
236                    Some(_) => return None,
237                }
238                let value = kv.1.as_ref().map_or(Some(Self::Unit), |t| {
239                    Self::from_datatype_in(t, structs, open)
240                })?;
241                Some(Self::Map(Box::new(value)))
242            }
243            DataType::Struct(id) => {
244                if open.contains(id) {
245                    return None;
246                }
247                let declared = structs.get(*id as usize)?;
248                open.push(*id);
249                let fields = declared
250                    .fields
251                    .iter()
252                    .map(|(name, t, _)| {
253                        Some((name.to_string(), Self::from_datatype_in(t, structs, open)?))
254                    })
255                    .collect::<Option<Vec<_>>>();
256                open.pop();
257                fields.map(Self::Struct)
258            }
259            _ => None,
260        }
261    }
262
263    /// Whether a struct is part of this shape, which only the host's side of
264    /// a call can take.
265    fn holds_struct(&self) -> bool {
266        match self {
267            Self::Struct(_) => true,
268            Self::Array(inner) | Self::Map(inner) => inner.holds_struct(),
269            Self::Int | Self::Float | Self::Bool | Self::String | Self::Unit => false,
270        }
271    }
272
273    #[must_use]
274    pub fn describe(&self) -> String {
275        match self {
276            Self::Int => "int".to_owned(),
277            Self::Float => "float".to_owned(),
278            Self::Bool => "bool".to_owned(),
279            Self::String => "string".to_owned(),
280            Self::Unit => "null".to_owned(),
281            Self::Array(inner) => format!("{}[]", inner.describe()),
282            Self::Map(value) => format!("{{string: {}}}", value.describe()),
283            Self::Struct(fields) => {
284                let fields: Vec<String> = fields
285                    .iter()
286                    .map(|(name, t)| format!("{name}: {}", t.describe()))
287                    .collect();
288                format!("struct {{ {} }}", fields.join(", "))
289            }
290        }
291    }
292}
293
294/// Whether a declared parameter type can be filled from a host [`Value`].
295///
296/// This is the gate `candela build` applies when it decides which functions get
297/// a host-callable export, and it admits exactly the types
298/// [`value_matches_type`] can check an argument against. Struct, enum and
299/// function parameters are left out: a host has no way to build one.
300#[must_use]
301pub fn is_host_callable_type(ty: &DataType) -> bool {
302    match ty {
303        DataType::Int
304        | DataType::Float
305        | DataType::Bool
306        | DataType::String
307        | DataType::Null
308        | DataType::Unknown => true,
309        DataType::Array(element) => element.as_deref().is_none_or(is_host_callable_type),
310        DataType::Map(kv) => {
311            matches!(&kv.0, None | Some(DataType::String))
312                && kv.1.as_ref().is_none_or(is_host_callable_type)
313        }
314        DataType::Union(members) => members.iter().all(is_host_callable_type),
315        DataType::Fn(_) | DataType::FnValue(_) | DataType::Struct(_) | DataType::Enum(_) => false,
316    }
317}
318
319/// Whether a host [`Value`] is an enum, or carries one inside it.
320///
321/// An enum crosses the boundary outward only: a script hands one back, and no
322/// parameter accepts one on the way in. Asking the value rather than the
323/// declared type is what covers the positions a type check cannot refuse on its
324/// own: an `any` parameter, and an array or map whose element type was never
325/// pinned.
326#[must_use]
327pub fn holds_enum(value: &Value) -> bool {
328    match value {
329        Value::Enum { .. } => true,
330        Value::Array(items) => items.iter().any(holds_enum),
331        Value::Map(entries) => entries.values().any(holds_enum),
332        Value::Null | Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::String(_) => false,
333    }
334}
335
336/// Names the shape of a host [`Value`] for an argument-mismatch report.
337#[must_use]
338pub fn describe_value(value: &Value) -> String {
339    match value {
340        Value::Null => String::from("null"),
341        Value::Int(_) => String::from("int"),
342        Value::Float(_) => String::from("float"),
343        Value::Bool(_) => String::from("bool"),
344        Value::String(_) => String::from("string"),
345        Value::Array(_) => String::from("array"),
346        Value::Map(_) => String::from("map"),
347        Value::Enum { variant, .. } => format!("enum variant {variant}"),
348    }
349}
350
351/// Whether a host [`Value`] satisfies a declared parameter type.
352///
353/// `any` (candela's [`DataType::Unknown`]) accepts every value except an enum;
354/// a union accepts a value any member accepts; an unparameterised array or map
355/// accepts any array or map, because the element type was never pinned. Paired
356/// with [`is_host_callable_type`], which decides which parameter types reach
357/// here at all.
358#[must_use]
359pub fn value_matches_type(value: &Value, ty: &DataType) -> bool {
360    // No parameter type accepts an enum, `any` and an unpinned collection
361    // included, so the value is asked first and every arm below answers for a
362    // value with no enum in it.
363    if holds_enum(value) {
364        return false;
365    }
366    match ty {
367        DataType::Unknown => true,
368        DataType::Int => matches!(value, Value::Int(_)),
369        DataType::Float => matches!(value, Value::Float(_)),
370        DataType::Bool => matches!(value, Value::Bool(_)),
371        DataType::String => matches!(value, Value::String(_)),
372        DataType::Null => matches!(value, Value::Null),
373        DataType::Array(element) => match value {
374            Value::Array(items) => element
375                .as_deref()
376                .is_none_or(|el| items.iter().all(|item| value_matches_type(item, el))),
377            _ => false,
378        },
379        DataType::Map(kv) => match value {
380            Value::Map(entries) => {
381                kv.1.as_ref()
382                    .is_none_or(|v| entries.values().all(|entry| value_matches_type(entry, v)))
383            }
384            _ => false,
385        },
386        DataType::Union(members) => members
387            .iter()
388            .any(|member| value_matches_type(value, member)),
389        DataType::Fn(_) | DataType::FnValue(_) | DataType::Struct(_) | DataType::Enum(_) => false,
390    }
391}
392
393/// The type-erased closure the VM dispatches a `host` call to.
394pub type HostDispatch = Rc<dyn Fn(&[Value]) -> Result<Value, HostError>>;
395
396/// Why a registered host function failed.
397///
398/// A closure returns this in place of a value to raise inside the script that
399/// called it. The VM reports it the way it reports its own runtime failures,
400/// naming the function and pointing at the call, and a script can catch it
401/// under the kind `host_fn_error`.
402///
403/// [`HostError::new`] takes anything that renders, so an error from the work
404/// the closure was doing propagates with `map_err(HostError::new)`.
405#[derive(Debug, Clone, PartialEq, Eq)]
406pub struct HostError {
407    message: String,
408}
409
410impl HostError {
411    /// Builds an error from anything that renders.
412    pub fn new(message: impl fmt::Display) -> Self {
413        Self {
414            message: message.to_string(),
415        }
416    }
417
418    /// What the host reported, as the diagnostic will read it.
419    #[must_use]
420    pub fn message(&self) -> &str {
421        &self.message
422    }
423}
424
425impl From<String> for HostError {
426    fn from(message: String) -> Self {
427        Self { message }
428    }
429}
430
431impl From<&str> for HostError {
432    fn from(message: &str) -> Self {
433        Self {
434            message: message.to_owned(),
435        }
436    }
437}
438
439impl fmt::Display for HostError {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        f.write_str(&self.message)
442    }
443}
444
445impl std::error::Error for HostError {}
446
447/// A registered host function: its erased dispatcher plus the argument/return
448/// type signature derived from the closure, used to validate it against the
449/// `host` block that declares it.
450struct RegisteredFn {
451    func: HostDispatch,
452    arg_types: Vec<HostType>,
453    ret_type: HostType,
454    /// Registered through [`HostRegistry::register_host_fn_variadic`]: the
455    /// closure takes a `&[Value]` slice of any length, so `arg_types`/`ret_type`
456    /// are unused and signature validation is skipped (the block must declare
457    /// the function with `...`).
458    variadic: bool,
459}
460
461/// The table of Rust closures a script's `host` blocks bind to.
462///
463/// Both halves of the toolchain bind through this one table. The `candela`
464/// crate's `Engine` binds at compile time, against the `host` signatures a
465/// fresh compile produced; [`crate::artifact::load_program`] binds at load
466/// time, against the signatures a `.cdlb` recorded. The checks are the same in
467/// both cases, so an artifact that loads is bound as strictly as a script that
468/// compiles.
469#[derive(Default)]
470pub struct HostRegistry {
471    fns: HashMap<(String, String), RegisteredFn>,
472}
473
474impl HostRegistry {
475    #[must_use]
476    pub fn new() -> Self {
477        Self::default()
478    }
479
480    /// Registers a typed host function under `namespace::name`.
481    ///
482    /// The closure may take any combination of `i64`/`i32`, `f64`, `bool`,
483    /// `String` (or a single `&str`), `Vec<T>` and string-keyed map arguments,
484    /// and return one of those or `()`. A closure that can fail returns
485    /// `Result<T, HostError>` instead, and the error is raised in the script
486    /// that called it. The derived types are checked against the `host` block
487    /// when the script is bound; a mismatch is an error, never a panic.
488    pub fn register_host_fn<Marker, F>(&mut self, namespace: &str, name: &str, f: F)
489    where
490        F: IntoHostFn<Marker>,
491    {
492        let (func, arg_types, ret_type) = f.into_host_fn_parts();
493        self.fns.insert(
494            (namespace.to_owned(), name.to_owned()),
495            RegisteredFn {
496                func,
497                arg_types,
498                ret_type,
499                variadic: false,
500            },
501        );
502    }
503
504    /// Registers a host function whose signature is given as data.
505    ///
506    /// The closure takes the arguments as a `&[Value]` slice, as a variadic one
507    /// does, but `arg_types` and `ret_type` pin the signature, so the binding is
508    /// checked against the `host` block exactly as [`register_host_fn`] is and
509    /// the declaration must not use `...`. This is what a host registers
510    /// through when the signature is only known at run time: a plugin table, a
511    /// generated binding, anything with no Rust type to derive it from.
512    ///
513    /// [`register_host_fn`]: HostRegistry::register_host_fn
514    pub fn register_host_fn_typed<F>(
515        &mut self,
516        namespace: &str,
517        name: &str,
518        arg_types: Vec<HostType>,
519        ret_type: HostType,
520        f: F,
521    ) where
522        F: Fn(&[Value]) -> Result<Value, HostError> + 'static,
523    {
524        self.fns.insert(
525            (namespace.to_owned(), name.to_owned()),
526            RegisteredFn {
527                func: Rc::new(f),
528                arg_types,
529                ret_type,
530                variadic: false,
531            },
532        );
533    }
534
535    /// Registers a variadic host function under `namespace::name`.
536    ///
537    /// The closure receives every argument as a `&[Value]` slice of any length
538    /// and returns one [`Value`], or a [`HostError`] to raise in the script that
539    /// called it, so arguments of mixed or dynamically-typed shape cross the
540    /// boundary without a fixed Rust signature. The `host` block must declare
541    /// the function with a `...` argument list, and no arity or per-argument
542    /// type checking happens at the call site.
543    pub fn register_host_fn_variadic<F>(&mut self, namespace: &str, name: &str, f: F)
544    where
545        F: Fn(&[Value]) -> Result<Value, HostError> + 'static,
546    {
547        self.fns.insert(
548            (namespace.to_owned(), name.to_owned()),
549            RegisteredFn {
550                func: Rc::new(f),
551                arg_types: Vec::new(),
552                ret_type: HostType::Unit,
553                variadic: true,
554            },
555        );
556    }
557
558    /// Binds every declared `host` signature to its registered closure,
559    /// returning the dispatchers in host-function id order.
560    ///
561    /// # Errors
562    ///
563    /// Returns [`HostBindError::Unregistered`] naming every declared function
564    /// with no closure behind it, or [`HostBindError::SignatureMismatch`] for
565    /// the first closure whose arity or types disagree with the declaration.
566    pub fn bind(
567        &self,
568        sigs: &[HostFnSig],
569        structs: &[Struct],
570    ) -> Result<Vec<HostDispatch>, HostBindError> {
571        let missing: Vec<String> = sigs
572            .iter()
573            .filter(|sig| !self.fns.contains_key(&key_of(sig)))
574            .map(|sig| qualified_name(&sig.namespace, &sig.name))
575            .collect();
576        if !missing.is_empty() {
577            return Err(HostBindError::Unregistered(missing));
578        }
579
580        let mut dispatch = Vec::with_capacity(sigs.len());
581        for sig in sigs {
582            let registered = &self.fns[&key_of(sig)];
583            validate_host_fn(sig, registered, structs)?;
584            dispatch.push(Rc::clone(&registered.func));
585        }
586        Ok(dispatch)
587    }
588}
589
590/// The lookup key a `host` signature binds under.
591fn key_of(sig: &HostFnSig) -> (String, String) {
592    (sig.namespace.to_string(), sig.name.to_string())
593}
594
595/// Renders a host function the way a script writes the call.
596pub(crate) fn qualified_name(namespace: &str, name: &str) -> String {
597    if namespace.is_empty() {
598        name.to_owned()
599    } else {
600        format!("{namespace}::{name}")
601    }
602}
603
604/// Checks that a registered closure's derived signature matches the `host`
605/// block declaration it is bound to.
606fn validate_host_fn(
607    sig: &HostFnSig,
608    registered: &RegisteredFn,
609    structs: &[Struct],
610) -> Result<(), HostBindError> {
611    let function = qualified_name(&sig.namespace, &sig.name);
612    let err = HostBindError::SignatureMismatch;
613
614    // A variadic declaration must be bound to a variadic closure and vice
615    // versa; when both agree there is nothing to check, the closure accepts
616    // any argument slice.
617    if sig.variadic || registered.variadic {
618        if sig.variadic != registered.variadic {
619            let (decl, reg) = if sig.variadic {
620                ("variadic (`...`)", "a fixed signature")
621            } else {
622                ("a fixed signature", "variadic")
623            };
624            return Err(err(format!(
625                "host function `{function}` is declared with {decl} but the registered closure has {reg}",
626            )));
627        }
628        return Ok(());
629    }
630
631    if sig.arg_count() != registered.arg_types.len() {
632        return Err(err(format!(
633            "host function `{function}` is declared with {} argument(s) but the registered closure takes {}",
634            sig.arg_count(),
635            registered.arg_types.len(),
636        )));
637    }
638
639    for (idx, want) in registered.arg_types.iter().enumerate() {
640        let declared = HostType::from_datatype(sig.get_arg(idx), structs).ok_or_else(|| {
641            err(format!(
642                "host function `{function}` argument {} has a type that cannot cross the host boundary",
643                idx + 1,
644            ))
645        })?;
646        if declared != *want {
647            return Err(err(format!(
648                "host function `{function}` argument {} is declared `{}` but the registered closure expects `{}`",
649                idx + 1,
650                declared.describe(),
651                want.describe(),
652            )));
653        }
654    }
655
656    // A struct crosses into the host only: the value a host returns would be
657    // marshalled as a map, not as the struct the declaration promises.
658    let declared_ret = HostType::from_datatype(sig.get_return_type(), structs)
659        .filter(|t| !t.holds_struct())
660        .ok_or_else(|| {
661            err(format!(
662                "host function `{function}` has a return type that cannot cross the host boundary",
663            ))
664        })?;
665    if declared_ret != registered.ret_type {
666        return Err(err(format!(
667            "host function `{function}` is declared to return `{}` but the registered closure returns `{}`",
668            declared_ret.describe(),
669            registered.ret_type.describe(),
670        )));
671    }
672
673    Ok(())
674}
675
676/// Why a script's `host` blocks could not be bound to a [`HostRegistry`].
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum HostBindError {
679    /// Declared `host` functions with no registered closure, in declaration
680    /// order.
681    Unregistered(Vec<String>),
682    /// A registered closure disagrees with the declaration it is bound to. The
683    /// text names the function and what differs.
684    SignatureMismatch(String),
685}
686
687impl HostBindError {
688    /// The stable identifier a `Diagnostic` reports this under.
689    #[must_use]
690    pub const fn code(&self) -> &'static str {
691        match self {
692            Self::Unregistered(_) => "unregistered_host_fn",
693            Self::SignatureMismatch(_) => "host_fn_signature_mismatch",
694        }
695    }
696}
697
698impl fmt::Display for HostBindError {
699    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700        match self {
701            Self::Unregistered(names) => {
702                if let [name] = names.as_slice() {
703                    write!(
704                        f,
705                        "no host function registered for `{name}` (declared in a `host` block)"
706                    )
707                } else {
708                    write!(
709                        f,
710                        "no host functions registered for `{}` (declared in `host` blocks)",
711                        names.join("`, `")
712                    )
713                }
714            }
715            Self::SignatureMismatch(message) => f.write_str(message),
716        }
717    }
718}
719
720impl std::error::Error for HostBindError {}
721
722/// Extracts a Rust argument from a [`Value`] for a registered host closure.
723pub trait FromHostValue: Sized {
724    fn from_host_value(v: &Value) -> Self;
725    fn host_type() -> HostType;
726}
727
728impl FromHostValue for i64 {
729    fn from_host_value(v: &Value) -> Self {
730        v.as_i64().unwrap_or(0)
731    }
732    fn host_type() -> HostType {
733        HostType::Int
734    }
735}
736impl FromHostValue for i32 {
737    fn from_host_value(v: &Value) -> Self {
738        v.as_i64().unwrap_or(0) as Self
739    }
740    fn host_type() -> HostType {
741        HostType::Int
742    }
743}
744impl FromHostValue for f64 {
745    fn from_host_value(v: &Value) -> Self {
746        v.as_f64().unwrap_or(0.0)
747    }
748    fn host_type() -> HostType {
749        HostType::Float
750    }
751}
752impl FromHostValue for bool {
753    fn from_host_value(v: &Value) -> Self {
754        v.as_bool().unwrap_or(false)
755    }
756    fn host_type() -> HostType {
757        HostType::Bool
758    }
759}
760impl FromHostValue for String {
761    fn from_host_value(v: &Value) -> Self {
762        v.as_str().unwrap_or_default().to_owned()
763    }
764    fn host_type() -> HostType {
765        HostType::String
766    }
767}
768impl<T: FromHostValue> FromHostValue for Vec<T> {
769    fn from_host_value(v: &Value) -> Self {
770        v.as_array()
771            .map(|items| items.iter().map(T::from_host_value).collect())
772            .unwrap_or_default()
773    }
774    fn host_type() -> HostType {
775        HostType::Array(Box::new(T::host_type()))
776    }
777}
778impl<T: FromHostValue> FromHostValue for BTreeMap<String, T> {
779    fn from_host_value(v: &Value) -> Self {
780        v.as_map()
781            .map(|m| {
782                m.iter()
783                    .map(|(k, val)| (k.clone(), T::from_host_value(val)))
784                    .collect()
785            })
786            .unwrap_or_default()
787    }
788    fn host_type() -> HostType {
789        HostType::Map(Box::new(T::host_type()))
790    }
791}
792impl<T: FromHostValue, S: std::hash::BuildHasher + Default> FromHostValue
793    for HashMap<String, T, S>
794{
795    fn from_host_value(v: &Value) -> Self {
796        v.as_map()
797            .map(|m| {
798                m.iter()
799                    .map(|(k, val)| (k.clone(), T::from_host_value(val)))
800                    .collect()
801            })
802            .unwrap_or_default()
803    }
804    fn host_type() -> HostType {
805        HostType::Map(Box::new(T::host_type()))
806    }
807}
808
809/// Converts a host closure's return value into a [`Value`].
810pub trait IntoHostValue {
811    fn into_host_value(self) -> Value;
812    fn host_type() -> HostType;
813}
814
815impl IntoHostValue for i64 {
816    fn into_host_value(self) -> Value {
817        Value::Int(self)
818    }
819    fn host_type() -> HostType {
820        HostType::Int
821    }
822}
823impl IntoHostValue for i32 {
824    fn into_host_value(self) -> Value {
825        Value::Int(i64::from(self))
826    }
827    fn host_type() -> HostType {
828        HostType::Int
829    }
830}
831impl IntoHostValue for f64 {
832    fn into_host_value(self) -> Value {
833        Value::Float(self)
834    }
835    fn host_type() -> HostType {
836        HostType::Float
837    }
838}
839impl IntoHostValue for bool {
840    fn into_host_value(self) -> Value {
841        Value::Bool(self)
842    }
843    fn host_type() -> HostType {
844        HostType::Bool
845    }
846}
847impl IntoHostValue for String {
848    fn into_host_value(self) -> Value {
849        Value::String(self)
850    }
851    fn host_type() -> HostType {
852        HostType::String
853    }
854}
855impl IntoHostValue for &str {
856    fn into_host_value(self) -> Value {
857        Value::String(self.to_owned())
858    }
859    fn host_type() -> HostType {
860        HostType::String
861    }
862}
863impl IntoHostValue for () {
864    fn into_host_value(self) -> Value {
865        Value::Null
866    }
867    fn host_type() -> HostType {
868        HostType::Unit
869    }
870}
871impl<T: IntoHostValue> IntoHostValue for Vec<T> {
872    fn into_host_value(self) -> Value {
873        Value::Array(
874            self.into_iter()
875                .map(IntoHostValue::into_host_value)
876                .collect(),
877        )
878    }
879    fn host_type() -> HostType {
880        HostType::Array(Box::new(T::host_type()))
881    }
882}
883impl<T: IntoHostValue> IntoHostValue for BTreeMap<String, T> {
884    fn into_host_value(self) -> Value {
885        Value::Map(
886            self.into_iter()
887                .map(|(k, v)| (k, v.into_host_value()))
888                .collect(),
889        )
890    }
891    fn host_type() -> HostType {
892        HostType::Map(Box::new(T::host_type()))
893    }
894}
895impl<T: IntoHostValue, S: std::hash::BuildHasher> IntoHostValue for HashMap<String, T, S> {
896    fn into_host_value(self) -> Value {
897        Value::Map(
898            self.into_iter()
899                .map(|(k, v)| (k, v.into_host_value()))
900                .collect(),
901        )
902    }
903    fn host_type() -> HostType {
904        HostType::Map(Box::new(T::host_type()))
905    }
906}
907
908/// What a registered host closure returns: a value, or an error to raise in the
909/// script that called it.
910///
911/// Implemented for every [`IntoHostValue`], so an infallible closure returns a
912/// plain `i64`/`String`/`Vec<T>`/..., and for `Result<T, HostError>`, so a
913/// fallible one returns that instead. Either way the host type checked against
914/// the `host` block is `T`'s.
915pub trait IntoHostResult {
916    fn into_host_result(self) -> Result<Value, HostError>;
917    fn host_type() -> HostType;
918}
919
920impl<T: IntoHostValue> IntoHostResult for T {
921    fn into_host_result(self) -> Result<Value, HostError> {
922        Ok(self.into_host_value())
923    }
924    fn host_type() -> HostType {
925        <T as IntoHostValue>::host_type()
926    }
927}
928
929impl<T: IntoHostValue> IntoHostResult for Result<T, HostError> {
930    fn into_host_result(self) -> Result<Value, HostError> {
931        self.map(IntoHostValue::into_host_value)
932    }
933    fn host_type() -> HostType {
934        <T as IntoHostValue>::host_type()
935    }
936}
937
938/// Adapts a Rust closure into a registered host function.
939///
940/// The `Marker` type parameter disambiguates the blanket impls by arity (and,
941/// for `&str`, by borrow), the same trick `rhai`/`bevy` use to make
942/// `register_fn` accept closures of many shapes without annotations.
943pub trait IntoHostFn<Marker> {
944    /// Internal adapter: yields the erased dispatcher plus the argument and
945    /// return type signature derived from the closure. Not meant to be called
946    /// directly; `Engine::register_host_fn` drives it.
947    fn into_host_fn_parts(self) -> (HostDispatch, Vec<HostType>, HostType);
948}
949
950/// Marker for a nullary closure.
951pub struct Arity0;
952/// Marker for a closure whose sole parameter is a borrowed `&str`.
953pub struct ArityStr1;
954
955impl<F, R> IntoHostFn<Arity0> for F
956where
957    F: Fn() -> R + 'static,
958    R: IntoHostResult,
959{
960    fn into_host_fn_parts(self) -> (HostDispatch, Vec<HostType>, HostType) {
961        (
962            Rc::new(move |_args: &[Value]| self().into_host_result()),
963            Vec::new(),
964            <R as IntoHostResult>::host_type(),
965        )
966    }
967}
968
969impl<F, R> IntoHostFn<ArityStr1> for F
970where
971    F: Fn(&str) -> R + 'static,
972    R: IntoHostResult,
973{
974    fn into_host_fn_parts(self) -> (HostDispatch, Vec<HostType>, HostType) {
975        (
976            Rc::new(move |args: &[Value]| {
977                let s = args.first().and_then(Value::as_str).unwrap_or_default();
978                self(s).into_host_result()
979            }),
980            vec![HostType::String],
981            <R as IntoHostResult>::host_type(),
982        )
983    }
984}
985
986/// Generates a `IntoHostFn` impl for an owned-argument closure of a given arity.
987/// Each argument marker is the tuple of its parameter types, which keeps the
988/// impls disjoint from one another and from the `&str`/nullary specializations.
989macro_rules! impl_into_host_fn {
990    ($($ty:ident $idx:tt),+) => {
991        impl<F, R, $($ty,)+> IntoHostFn<($($ty,)+)> for F
992        where
993            F: Fn($($ty,)+) -> R + 'static,
994            R: IntoHostResult,
995            $( $ty: FromHostValue + 'static, )+
996        {
997            fn into_host_fn_parts(self) -> (HostDispatch, Vec<HostType>, HostType) {
998                (
999                    Rc::new(move |args: &[Value]| {
1000                        self( $( <$ty as FromHostValue>::from_host_value(&args[$idx]), )+ )
1001                            .into_host_result()
1002                    }),
1003                    vec![ $( <$ty as FromHostValue>::host_type(), )+ ],
1004                    <R as IntoHostResult>::host_type(),
1005                )
1006            }
1007        }
1008    };
1009}
1010
1011impl_into_host_fn!(A0 0);
1012impl_into_host_fn!(A0 0, A1 1);
1013impl_into_host_fn!(A0 0, A1 1, A2 2);
1014impl_into_host_fn!(A0 0, A1 1, A2 2, A3 3);
1015impl_into_host_fn!(A0 0, A1 1, A2 2, A3 3, A4 4);
1016
1017/// Allocates `v` into candela's heap pools and returns the value to store.
1018///
1019/// Scalars become NaN-boxed values directly. A list or map takes its slot the
1020/// way one the program builds does, reusing a freed slot when there is one,
1021/// so building it can start or carry on a collection. Everything built so far
1022/// therefore stays reachable from the roots a collection reads, `registers`
1023/// and `recursion_stack`: the outermost container sits on top of
1024/// `recursion_stack` while it is filled, and each entry goes into its parent
1025/// before anything inside it is allocated. The caller keeps every other value
1026/// the program still holds reachable from the roots too, and stores the
1027/// result before it allocates again.
1028///
1029/// A string is interned: one the pool already holds by its text is shared,
1030/// since a map finds a string key by its slot.
1031pub fn marshal_value(
1032    v: &Value,
1033    registers: &RegisterFile,
1034    recursion_stack: &mut RegisterFile,
1035    objs: &mut ObjectPool,
1036    maps: &mut MapPool,
1037    strings: &mut StringPool,
1038    gc: &mut GcState,
1039) -> Data {
1040    let mut heap = Heap {
1041        objs,
1042        maps,
1043        strings,
1044        gc,
1045        registers,
1046        recursion_stack,
1047    };
1048    let d = shell(&mut heap, v);
1049    if matches!(v, Value::Array(_) | Value::Map(_)) {
1050        heap.recursion_stack.0.push(d);
1051        fill(&mut heap, v, d);
1052        heap.recursion_stack.0.pop();
1053    }
1054    d
1055}
1056
1057/// Writes each of `args` into the matching register of `arg_registers`, with
1058/// the register file as the only roots: what a host does between runs.
1059pub fn marshal_args(
1060    args: &[Value],
1061    arg_registers: &[u16],
1062    registers: &mut Vec<Data>,
1063    pools: &mut Pools,
1064) {
1065    let mut file = RegisterFile(std::mem::take(registers));
1066    let mut stack = RegisterFile(Vec::new());
1067    for (arg, &register) in args.iter().zip(arg_registers) {
1068        let d = marshal_value(
1069            arg,
1070            &file,
1071            &mut stack,
1072            &mut pools.objs,
1073            &mut pools.maps,
1074            &mut pools.strings,
1075            &mut pools.gc,
1076        );
1077        file.0[register as usize] = d;
1078    }
1079    *registers = file.0;
1080}
1081
1082/// `v` itself: a scalar, a string, or an empty list or map for [`fill`] to
1083/// fill in.
1084fn shell(heap: &mut Heap<'_>, v: &Value) -> Data {
1085    match v {
1086        Value::Null => NULL,
1087        Value::Int(i) => Data::int(*i),
1088        Value::Float(f) => Data::float(*f),
1089        Value::Bool(b) => Data::bool(*b),
1090        Value::String(s) => heap.string(s),
1091        Value::Array(_) => heap.array(),
1092        Value::Map(_) => heap.map(),
1093        // Enums travel outward only: both embedding paths refuse an argument
1094        // that holds one (see `holds_enum`) before anything is marshalled, so
1095        // this arm is what keeps the match exhaustive rather than a conversion
1096        // anything reaches.
1097        Value::Enum { .. } => NULL,
1098    }
1099}
1100
1101/// Fills `d`, the shell [`shell`] made for `v` and already reachable from the
1102/// roots. Each entry goes in before anything inside it is allocated.
1103fn fill(heap: &mut Heap<'_>, v: &Value, d: Data) {
1104    match v {
1105        Value::Array(items) => {
1106            let id = d.as_array();
1107            heap.objs[id].reserve(items.len());
1108            for item in items {
1109                let child = shell(heap, item);
1110                heap.objs[id].push(child);
1111                fill(heap, item, child);
1112            }
1113        }
1114        Value::Map(entries) => {
1115            let id = d.as_map();
1116            for (k, val) in entries {
1117                let key = heap.string(k);
1118                map_insert(&mut heap.maps[id], key, NULL, heap.strings);
1119                let child = shell(heap, val);
1120                map_insert(&mut heap.maps[id], key, child, heap.strings);
1121                fill(heap, val, child);
1122            }
1123        }
1124        _ => {}
1125    }
1126}
1127
1128/// Reads a runtime [`Data`] back into a host [`Value`], recursively for arrays,
1129/// maps, structs and enums. Arrays, structs and enums live in the shared object
1130/// pool; structs are surfaced as string-keyed [`Value::Map`]s using their
1131/// declared field names, and an enum becomes a [`Value::Enum`] naming the
1132/// variant it holds. Map keys that are not strings are skipped.
1133pub fn unmarshal_value(
1134    d: Data,
1135    objs: &ObjectPool,
1136    maps: &MapPool,
1137    strings: &StringPool,
1138    structs: &[Struct],
1139    enums: &[EnumType],
1140) -> Value {
1141    if d.is_int() {
1142        Value::Int(d.as_int())
1143    } else if d.is_bool() {
1144        Value::Bool(d.as_bool())
1145    } else if d.is_string() {
1146        Value::String(d.as_str(strings).to_owned())
1147    } else if d.is_null() {
1148        Value::Null
1149    } else if d.is_array() {
1150        let items = objs[d.as_array()]
1151            .iter()
1152            .map(|e| unmarshal_value(*e, objs, maps, strings, structs, enums))
1153            .collect();
1154        Value::Array(items)
1155    } else if d.is_struct() {
1156        let fields = &structs[d.struct_type_id() as usize].fields;
1157        let values = &objs[d.as_struct()];
1158        let record = fields
1159            .iter()
1160            .zip(values.iter())
1161            .map(|((name, _, _), val)| {
1162                (
1163                    name.to_string(),
1164                    unmarshal_value(*val, objs, maps, strings, structs, enums),
1165                )
1166            })
1167            .collect();
1168        Value::Map(record)
1169    } else if d.is_enum() {
1170        // An enum instance is `[tag, payload...]` in the object pool; the tag
1171        // indexes the variant list of its declared type.
1172        let variants = &enums[d.enum_type_id() as usize].variants;
1173        let entry = &objs[d.as_enum()];
1174        let tag = entry[0].as_int() as usize;
1175        Value::Enum {
1176            variant: variants[tag].name.to_string(),
1177            payload: entry[1..]
1178                .iter()
1179                .map(|val| unmarshal_value(*val, objs, maps, strings, structs, enums))
1180                .collect(),
1181        }
1182    } else if d.is_map() {
1183        let record = maps[d.as_map()]
1184            .iter()
1185            .filter(|(k, _)| k.is_string())
1186            .map(|(k, val)| {
1187                (
1188                    k.as_str(strings).to_owned(),
1189                    unmarshal_value(*val, objs, maps, strings, structs, enums),
1190                )
1191            })
1192            .collect();
1193        Value::Map(record)
1194    } else if d.is_float() {
1195        Value::Float(d.as_float())
1196    } else {
1197        Value::Null
1198    }
1199}