Skip to main content

adam_lang/
cell_value.rs

1//! Private canonical cell storage and construction-time frozen Adam/CEL adapters.
2//!
3//! Primitive cells remain concrete Rust values. Compound access passes through
4//! `ParsedSheet`'s checked representation-witness boundary.
5use std::any::{Any, TypeId};
6
7use cel_runtime::{DynSegment, OwnedValue, ValueType, prepared_segment::PreparedRawSegment};
8
9use crate::{TypeRegistry, type_registry::TypeShape};
10
11/// Formats a canonical compound cell at the checked host boundary.
12///
13/// This also supports hosts that retain the underlying `adam_rs::Sheet`.
14/// # Errors
15/// Rejects an unknown cell, concrete leaf storage, or absent debug capability.
16/// - Complexity: O(v + s) for v contained values and s output bytes.
17///
18/// # Examples
19/// ```
20/// let parsed = adam_lang::AdamParser::new(adam_lang::TypeRegistry::new(), cel_parser::OpLookup::new())
21///     .parse_str("sheet s { cell point: (i32, i32) = (3, 4); }").unwrap();
22/// assert_eq!(adam_lang::debug_compound_cell(&parsed.sheet, parsed.cell_names["point"].0).unwrap(), "(3, 4)");
23/// ```
24pub fn debug_compound_cell(sheet: &adam_rs::Sheet, id: adam_rs::CellId) -> anyhow::Result<String> {
25    sheet.read::<CellValue>(id)?.0.try_debug()
26}
27
28/// Prepares canonical compound-cell formatting from the cell owner's debug capability.
29///
30/// The returned closure reads the current value, checks its complete shape, and
31/// formats through the retained capability. It does not consume or clone values.
32///
33/// # Errors
34/// Rejects an unknown cell, noncompound storage, or unavailable debug capability.
35/// The closure also rejects a different shape or missing cell in its input sheet.
36/// - Complexity: Preparation O(1); each call O(n + v + s) for n type nodes,
37///   v contained values and s output bytes, excluding leaf formatter costs.
38///
39/// # Examples
40/// ```
41/// let parsed = adam_lang::AdamParser::new(adam_lang::TypeRegistry::new(), cel_parser::OpLookup::new())
42///     .parse_str("sheet s { cell xs: [i32]; }").unwrap();
43/// let format = adam_lang::prepare_compound_cell_formatter(&parsed, parsed.cell_names["xs"].0).unwrap();
44/// assert_eq!(format(&parsed).unwrap(), "[]");
45/// ```
46pub fn prepare_compound_cell_formatter(
47    sheet: &adam_rs::Sheet,
48    id: adam_rs::CellId,
49) -> anyhow::Result<impl Fn(&adam_rs::Sheet) -> anyhow::Result<String> + use<>> {
50    let formatter = sheet.read::<CellValue>(id)?.0.prepare_debug()?;
51    Ok(move |sheet: &adam_rs::Sheet| formatter.format(&sheet.read::<CellValue>(id)?.0))
52}
53
54/// Owns a compound value whose cloning and equality capabilities are bound.
55///
56/// Only checked host writes and frozen output adapters construct this private type.
57pub(crate) struct CellValue(pub(crate) OwnedValue);
58
59impl CellValue {
60    /// Rejects missing cell capabilities before sheet insertion.
61    ///
62    /// # Errors
63    /// Returns a diagnostic for missing cloning or equality.
64    pub(crate) fn validate(ty: &ValueType) -> anyhow::Result<()> {
65        anyhow::ensure!(
66            ty.can_clone_for_argument(),
67            "Adam cell clone capability unavailable for `{}`",
68            ty.type_name()
69        );
70        anyhow::ensure!(
71            ty.can_compare(),
72            "Adam cell equality capability unavailable for `{}`",
73            ty.type_name()
74        );
75        Ok(())
76    }
77}
78
79impl Clone for CellValue {
80    /// Clones through the previously validated frozen ownership plan.
81    ///
82    /// - Complexity: O(v) for v contained values, excluding leaf clone costs.
83    fn clone(&self) -> Self {
84        Self(self.0.try_clone().expect("validated cell clone capability"))
85    }
86}
87
88impl PartialEq for CellValue {
89    /// Compares values at a shape-validated cell boundary without repeating type checks.
90    ///
91    /// - Complexity: O(v) for v contained values, excluding leaf equality costs.
92    fn eq(&self, other: &Self) -> bool {
93        // SAFETY: This type is private; host writes and all prepared outputs bind
94        // the complete cell shape before entering Adam's type-erased storage.
95        unsafe { self.0.eq_bound(&other.0) }
96    }
97}
98
99impl std::fmt::Debug for CellValue {
100    /// Formats a value through its optional canonical debug capability.
101    ///
102    /// - Complexity: O(v + s) for v contained values and s formatted bytes.
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self.0.try_debug() {
105            Ok(value) => f.write_str(&value),
106            Err(_) => f.write_str("<compound value>"),
107        }
108    }
109}
110
111/// Describes a construction-time validated move from canonical result to Adam storage.
112enum Output {
113    /// A registered primitive or foreign concrete Rust leaf.
114    Leaf(unsafe fn(OwnedValue) -> Box<dyn Any>),
115    /// Private whole-value ownership of a recursive compound.
116    Compound,
117}
118
119impl Output {
120    /// Moves one already-bound canonical result into its Adam storage carrier.
121    fn store(&self, value: OwnedValue) -> Box<dyn Any> {
122        match self {
123            // SAFETY: The result identity was bound during construction.
124            Self::Leaf(box_value) => unsafe { box_value(value) },
125            Self::Compound => Box::new(CellValue(value)),
126        }
127    }
128}
129
130/// Executes a frozen canonical raw frame behind Adam's existing host callback boundary.
131pub(crate) struct Frame {
132    body: RawFrame,
133    outputs: Vec<Output>,
134    split: bool,
135}
136
137/// Binds Adam storage addresses to the canonical runtime's one raw execution engine.
138pub(crate) struct RawFrame {
139    body: PreparedRawSegment,
140    inputs: Vec<Input>,
141    pointers: Vec<*const u8>,
142}
143
144/// Retains the complete storage identity required at an erased host boundary.
145enum Input {
146    /// A concrete registered Rust leaf.
147    Leaf(TypeId),
148    /// A private carrier with an exact recursive canonical shape.
149    Compound(ValueType),
150}
151
152impl Input {
153    /// Checks storage identity and complete shape without binding or reading raw pointers.
154    ///
155    /// # Errors
156    /// Rejects a different concrete type, compound carrier, or recursive shape.
157    /// - Complexity: O(n) for n compound descriptor nodes; O(1) for leaves.
158    fn validate(&self, input: &dyn Any) -> anyhow::Result<()> {
159        let matches = match self {
160            Self::Leaf(id) => input.type_id() == *id,
161            Self::Compound(expected) => input
162                .downcast_ref::<CellValue>()
163                .is_some_and(|value| value.0.value_type().same_shape(expected)),
164        };
165        anyhow::ensure!(matches, "Adam argument storage or shape mismatch");
166        Ok(())
167    }
168}
169
170impl Frame {
171    /// Validates complete reads, outputs, and cell capabilities before execution.
172    ///
173    /// # Errors
174    /// Rejects mismatched recursive representations or missing required capabilities.
175    /// - Complexity: O(n) in signature nodes and emitted reads.
176    pub(crate) fn new(
177        body: DynSegment,
178        inputs: &[TypeShape],
179        outputs: &[TypeShape],
180        split: bool,
181        types: &TypeRegistry,
182    ) -> anyhow::Result<Self> {
183        let result = if split {
184            types.value_type(&TypeShape::Tuple(outputs.to_vec()))
185        } else {
186            types.value_type(&outputs[0])
187        };
188        let mut adapters = Vec::with_capacity(outputs.len());
189        for shape in outputs {
190            match shape {
191                TypeShape::Named(id) => adapters.push(Output::Leaf(
192                    types
193                        .entry_by_type_id(*id)
194                        .expect("resolved leaf")
195                        .box_owned,
196                )),
197                _ => {
198                    CellValue::validate(&types.value_type(shape))?;
199                    adapters.push(Output::Compound);
200                }
201            }
202        }
203        Ok(Self {
204            body: RawFrame::new(body, inputs, &result, types)?,
205            outputs: adapters,
206            split,
207        })
208    }
209
210    /// Executes raw borrowed inputs already type-checked by Adam's callback boundary.
211    ///
212    /// - Precondition: Input storage matches the exact shapes supplied to construction.
213    /// # Errors
214    /// Propagates CEL operation failures after canonical cleanup.
215    /// - Complexity: O(a) pointer binding plus body costs and output moves.
216    pub(crate) fn call(&mut self, inputs: &[&dyn Any]) -> anyhow::Result<Vec<Box<dyn Any>>> {
217        let result = self.body.call(inputs)?;
218        self.store(result)
219    }
220
221    /// Executes a filter frame without allocating a merged host argument list.
222    ///
223    /// - Precondition: The candidate and dependencies match the prepared storage types.
224    /// # Errors
225    /// Propagates operation failures.
226    /// - Complexity: O(a) pointer binding plus body costs and output moves.
227    pub(crate) fn call_with_prefix(
228        &mut self,
229        value: &dyn Any,
230        inputs: &[&dyn Any],
231    ) -> anyhow::Result<Vec<Box<dyn Any>>> {
232        let result = self.body.call_with_prefix(value, inputs)?;
233        self.store(result)
234    }
235
236    /// Moves a whole result or its bound fields into the requested output cells.
237    ///
238    /// # Errors
239    /// Returns an error if a destructuring result is not a tuple.
240    /// - Complexity: O(n + s) for n output fields and s representation bytes.
241    fn store(&self, result: OwnedValue) -> anyhow::Result<Vec<Box<dyn Any>>> {
242        if self.split {
243            Ok(result
244                .into_fields()?
245                .into_iter()
246                .zip(&self.outputs)
247                .map(|(value, output)| output.store(value))
248                .collect())
249        } else {
250            Ok(vec![self.outputs[0].store(result)])
251        }
252    }
253}
254
255impl RawFrame {
256    /// Validates every positional read and the result once before execution.
257    ///
258    /// # Errors
259    /// Rejects a mismatched signature or missing borrowed cloning.
260    /// - Complexity: O(n) in descriptor nodes and emitted reads.
261    pub(crate) fn new(
262        body: DynSegment,
263        inputs: &[TypeShape],
264        result: &ValueType,
265        types: &TypeRegistry,
266    ) -> anyhow::Result<Self> {
267        let input_types: Vec<_> = inputs.iter().map(|shape| types.value_type(shape)).collect();
268        Ok(Self {
269            body: PreparedRawSegment::new(body, &input_types, result)?,
270            inputs: inputs
271                .iter()
272                .zip(input_types)
273                .map(|(shape, value_type)| match shape {
274                    TypeShape::Named(id) => Input::Leaf(*id),
275                    _ => Input::Compound(value_type),
276                })
277                .collect(),
278            pointers: vec![std::ptr::null(); inputs.len()],
279        })
280    }
281
282    /// Executes an Adam-validated host frame without recursive checks or conversions.
283    ///
284    /// - Precondition: Every input matches its preparation-time storage adapter.
285    /// # Errors
286    /// Propagates operation failures after cleanup.
287    /// - Complexity: O(a) pointer binding plus body costs.
288    pub(crate) fn call(&mut self, inputs: &[&dyn Any]) -> anyhow::Result<OwnedValue> {
289        self.call_arguments(inputs.iter().copied(), inputs.len())
290    }
291
292    /// Prepends a filter candidate without allocating a merged host argument list.
293    ///
294    /// - Precondition: The candidate and dependencies match their prepared storage types.
295    /// # Errors
296    /// Propagates operation failures.
297    /// - Complexity: O(a) pointer binding plus body costs.
298    pub(crate) fn call_with_prefix(
299        &mut self,
300        value: &dyn Any,
301        inputs: &[&dyn Any],
302    ) -> anyhow::Result<OwnedValue> {
303        self.call_arguments(
304            std::iter::once(value).chain(inputs.iter().copied()),
305            inputs.len() + 1,
306        )
307    }
308
309    /// Checks every erased input before binding any address or executing the prefixed frame.
310    ///
311    /// # Errors
312    /// Rejects missing or extra inputs, wrong scalar identities, compound carriers, or
313    /// recursive shapes on every invocation; propagates operation failures after cleanup.
314    /// Rejection leaves previously bound pointers unused and permits later valid calls.
315    /// - Complexity: O(a + n) validation for a inputs and n compound descriptor nodes,
316    ///   plus pointer binding and body costs.
317    pub(crate) fn call_checked_with_prefix(
318        &mut self,
319        value: &dyn Any,
320        inputs: &[&dyn Any],
321    ) -> anyhow::Result<OwnedValue> {
322        anyhow::ensure!(
323            inputs.len().checked_add(1) == Some(self.inputs.len()),
324            "Adam argument count mismatch"
325        );
326        for (input, expected) in std::iter::once(value)
327            .chain(inputs.iter().copied())
328            .zip(&self.inputs)
329        {
330            expected.validate(input)?;
331        }
332        self.call_with_prefix(value, inputs)
333    }
334
335    /// Binds borrowed host addresses into the reusable canonical pointer slots.
336    ///
337    /// - Precondition: Input count and types match preparation.
338    /// # Errors
339    /// Propagates operation failures after raw frame cleanup.
340    /// - Complexity: O(a) pointer binding plus body costs.
341    fn call_arguments<'a>(
342        &mut self,
343        inputs: impl Iterator<Item = &'a dyn Any>,
344        arity: usize,
345    ) -> anyhow::Result<OwnedValue> {
346        debug_assert_eq!(arity, self.pointers.len());
347        for ((input, adapter), pointer) in inputs.zip(&self.inputs).zip(&mut self.pointers) {
348            let data = std::ptr::from_ref(input).cast::<()>();
349            *pointer = match adapter {
350                Input::Compound(_) => {
351                    // SAFETY: Adam's internal boundary or the checked public
352                    // adapter verifies the private carrier and complete shape.
353                    unsafe { &*data.cast::<CellValue>() }.0.as_ptr()
354                }
355                Input::Leaf(_) => data.cast(),
356            };
357        }
358        // SAFETY: Construction validates every read. The internal callback boundary
359        // or checked entry point guarantees storage types and complete compound shapes.
360        unsafe { self.body.call(&self.pointers) }
361    }
362}