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};
6use std::cell::RefCell;
7
8use adam_rs::{CellId, Requirement};
9
10use cel_runtime::{DynSegment, OwnedValue, ValueType, prepared_segment::PreparedRawSegment};
11
12use crate::{TypeRegistry, type_registry::TypeShape};
13
14/// Formats a canonical compound cell at the checked host boundary.
15///
16/// This also supports hosts that retain the underlying `adam_rs::Sheet`.
17/// # Errors
18/// Rejects an unknown cell, concrete leaf storage, or absent debug capability.
19/// - Complexity: O(v + s) for v contained values and s output bytes.
20///
21/// # Examples
22/// ```
23/// let parsed = adam_lang::AdamParser::new(adam_lang::TypeRegistry::new(), cel_parser::OpLookup::new())
24///     .parse_str("sheet s { cell point: (i32, i32) = (3, 4); }").unwrap();
25/// assert_eq!(adam_lang::debug_compound_cell(&parsed.sheet, parsed.cell_names["point"].0).unwrap(), "(3, 4)");
26/// ```
27pub fn debug_compound_cell(sheet: &adam_rs::Sheet, id: adam_rs::CellId) -> anyhow::Result<String> {
28    sheet.read::<CellValue>(id)?.0.try_debug()
29}
30
31/// Prepares canonical compound-cell formatting from the cell owner's debug capability.
32///
33/// The returned closure reads the current value, checks its complete shape, and
34/// formats through the retained capability. It does not consume or clone values.
35///
36/// # Errors
37/// Rejects an unknown cell, noncompound storage, or unavailable debug capability.
38/// The closure also rejects a different shape or missing cell in its input sheet.
39/// - Complexity: Preparation O(1); each call O(n + v + s) for n type nodes,
40///   v contained values and s output bytes, excluding leaf formatter costs.
41///
42/// # Examples
43/// ```
44/// let parsed = adam_lang::AdamParser::new(adam_lang::TypeRegistry::new(), cel_parser::OpLookup::new())
45///     .parse_str("sheet s { cell xs: [i32]; }").unwrap();
46/// let format = adam_lang::prepare_compound_cell_formatter(&parsed, parsed.cell_names["xs"].0).unwrap();
47/// assert_eq!(format(&parsed).unwrap(), "[]");
48/// ```
49pub fn prepare_compound_cell_formatter(
50    sheet: &adam_rs::Sheet,
51    id: adam_rs::CellId,
52) -> anyhow::Result<impl Fn(&adam_rs::Sheet) -> anyhow::Result<String> + use<>> {
53    let formatter = sheet.read::<CellValue>(id)?.0.prepare_debug()?;
54    Ok(move |sheet: &adam_rs::Sheet| formatter.format(&sheet.read::<CellValue>(id)?.0))
55}
56
57/// Owns a compound value whose cloning and equality capabilities are bound.
58///
59/// Only checked host writes and frozen output adapters construct this private type.
60pub(crate) struct CellValue(pub(crate) OwnedValue);
61
62impl CellValue {
63    /// Rejects missing cell capabilities before sheet insertion.
64    ///
65    /// # Errors
66    /// Returns a diagnostic for missing cloning or equality.
67    pub(crate) fn validate(ty: &ValueType) -> anyhow::Result<()> {
68        anyhow::ensure!(
69            ty.can_clone_for_argument(),
70            "Adam cell clone capability unavailable for `{}`",
71            ty.type_name()
72        );
73        anyhow::ensure!(
74            ty.can_compare(),
75            "Adam cell equality capability unavailable for `{}`",
76            ty.type_name()
77        );
78        Ok(())
79    }
80}
81
82impl Clone for CellValue {
83    /// Clones through the previously validated frozen ownership plan.
84    ///
85    /// - Complexity: O(v) for v contained values, excluding leaf clone costs.
86    fn clone(&self) -> Self {
87        Self(self.0.try_clone().expect("validated cell clone capability"))
88    }
89}
90
91impl PartialEq for CellValue {
92    /// Compares values at a shape-validated cell boundary without repeating type checks.
93    ///
94    /// - Complexity: O(v) for v contained values, excluding leaf equality costs.
95    fn eq(&self, other: &Self) -> bool {
96        // SAFETY: This type is private; host writes and all prepared outputs bind
97        // the complete cell shape before entering Adam's type-erased storage.
98        unsafe { self.0.eq_bound(&other.0) }
99    }
100}
101
102impl std::fmt::Debug for CellValue {
103    /// Formats a value through its optional canonical debug capability.
104    ///
105    /// - Complexity: O(v + s) for v contained values and s formatted bytes.
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self.0.try_debug() {
108            Ok(value) => f.write_str(&value),
109            Err(_) => f.write_str("<compound value>"),
110        }
111    }
112}
113
114/// Describes a construction-time validated move from canonical result to Adam storage.
115enum Output {
116    /// A registered primitive or foreign concrete Rust leaf.
117    Leaf(unsafe fn(OwnedValue) -> Box<dyn Any>),
118    /// Private whole-value ownership of a recursive compound.
119    Compound,
120}
121
122impl Output {
123    /// Moves one already-bound canonical result into its Adam storage carrier.
124    fn store(&self, value: OwnedValue) -> Box<dyn Any> {
125        match self {
126            // SAFETY: The result identity was bound during construction.
127            Self::Leaf(box_value) => unsafe { box_value(value) },
128            Self::Compound => Box::new(CellValue(value)),
129        }
130    }
131}
132
133/// Executes a frozen canonical raw frame behind Adam's existing host callback boundary.
134pub(crate) struct Frame {
135    body: RawFrame,
136    outputs: Vec<Output>,
137    split: bool,
138}
139
140/// Binds Adam storage addresses to the canonical runtime's one raw execution engine.
141pub(crate) struct RawFrame {
142    body: PreparedRawSegment,
143    inputs: Vec<Input>,
144    pointers: Vec<*const u8>,
145}
146
147/// Retains the complete storage identity required at an erased host boundary.
148enum Input {
149    /// A concrete registered Rust leaf.
150    Leaf(TypeId),
151    /// A private carrier with an exact recursive canonical shape.
152    Compound(ValueType),
153}
154
155impl Input {
156    /// Checks storage identity and complete shape without binding or reading raw pointers.
157    ///
158    /// # Errors
159    /// Rejects a different concrete type, compound carrier, or recursive shape.
160    /// - Complexity: O(n) for n compound descriptor nodes; O(1) for leaves.
161    fn validate(&self, input: &dyn Any) -> anyhow::Result<()> {
162        let matches = match self {
163            Self::Leaf(id) => input.type_id() == *id,
164            Self::Compound(expected) => input
165                .downcast_ref::<CellValue>()
166                .is_some_and(|value| value.0.value_type().same_shape(expected)),
167        };
168        anyhow::ensure!(matches, "Adam argument storage or shape mismatch");
169        Ok(())
170    }
171}
172
173impl Frame {
174    /// Validates complete reads, outputs, and cell capabilities before execution.
175    ///
176    /// # Errors
177    /// Rejects mismatched recursive representations or missing required capabilities.
178    /// - Complexity: O(n) in signature nodes and emitted reads.
179    pub(crate) fn new(
180        body: DynSegment,
181        inputs: &[TypeShape],
182        outputs: &[TypeShape],
183        split: bool,
184        types: &TypeRegistry,
185    ) -> anyhow::Result<Self> {
186        let result = if split {
187            types.value_type(&TypeShape::Tuple(outputs.to_vec()))
188        } else {
189            types.value_type(&outputs[0])
190        };
191        let mut adapters = Vec::with_capacity(outputs.len());
192        for shape in outputs {
193            match shape {
194                TypeShape::Named(id) => adapters.push(Output::Leaf(
195                    types
196                        .entry_by_type_id(*id)
197                        .expect("resolved leaf")
198                        .box_owned,
199                )),
200                _ => {
201                    CellValue::validate(&types.value_type(shape))?;
202                    adapters.push(Output::Compound);
203                }
204            }
205        }
206        Ok(Self {
207            body: RawFrame::new(body, inputs, &result, types)?,
208            outputs: adapters,
209            split,
210        })
211    }
212
213    /// Executes raw borrowed inputs already type-checked by Adam's callback boundary.
214    ///
215    /// - Precondition: Input storage matches the exact shapes supplied to construction.
216    /// # Errors
217    /// Propagates CEL operation failures after canonical cleanup.
218    /// - Complexity: O(a) pointer binding plus body costs and output moves.
219    pub(crate) fn call(&mut self, inputs: &[&dyn Any]) -> anyhow::Result<Vec<Box<dyn Any>>> {
220        let result = self.body.call(inputs)?;
221        self.store(result)
222    }
223
224    /// Executes a filter frame without allocating a merged host argument list.
225    ///
226    /// - Precondition: The candidate and dependencies match the prepared storage types.
227    /// # Errors
228    /// Propagates operation failures.
229    /// - Complexity: O(a) pointer binding plus body costs and output moves.
230    pub(crate) fn call_with_prefix(
231        &mut self,
232        value: &dyn Any,
233        inputs: &[&dyn Any],
234    ) -> anyhow::Result<Vec<Box<dyn Any>>> {
235        let result = self.body.call_with_prefix(value, inputs)?;
236        self.store(result)
237    }
238
239    /// Moves a whole result or its bound fields into the requested output cells.
240    ///
241    /// # Errors
242    /// Returns an error if a destructuring result is not a tuple.
243    /// - Complexity: O(n + s) for n output fields and s representation bytes.
244    fn store(&self, result: OwnedValue) -> anyhow::Result<Vec<Box<dyn Any>>> {
245        if self.split {
246            Ok(result
247                .into_fields()?
248                .into_iter()
249                .zip(&self.outputs)
250                .map(|(value, output)| output.store(value))
251                .collect())
252        } else {
253            Ok(vec![self.outputs[0].store(result)])
254        }
255    }
256}
257
258impl RawFrame {
259    /// Validates every positional read and binds the result's capabilities before execution.
260    ///
261    /// # Errors
262    /// Rejects a mismatched signature or missing borrowed cloning.
263    /// - Complexity: O(n) in descriptor nodes and emitted reads.
264    pub(crate) fn new(
265        mut body: DynSegment,
266        inputs: &[TypeShape],
267        result: &ValueType,
268        types: &TypeRegistry,
269    ) -> anyhow::Result<Self> {
270        let input_types: Vec<_> = inputs.iter().map(|shape| types.value_type(shape)).collect();
271        body.annotate_top(result.clone())?;
272        Ok(Self {
273            body: PreparedRawSegment::new(body, &input_types, result)?,
274            inputs: inputs
275                .iter()
276                .zip(input_types)
277                .map(|(shape, value_type)| match shape {
278                    TypeShape::Named(id) => Input::Leaf(*id),
279                    _ => Input::Compound(value_type),
280                })
281                .collect(),
282            pointers: vec![std::ptr::null(); inputs.len()],
283        })
284    }
285
286    /// Executes an Adam-validated host frame without recursive checks or conversions.
287    ///
288    /// - Precondition: Every input matches its preparation-time storage adapter.
289    /// # Errors
290    /// Propagates operation failures after cleanup.
291    /// - Complexity: O(a) pointer binding plus body costs.
292    pub(crate) fn call(&mut self, inputs: &[&dyn Any]) -> anyhow::Result<OwnedValue> {
293        self.call_arguments(inputs.iter().copied(), inputs.len())
294    }
295
296    /// Prepends a filter candidate without allocating a merged host argument list.
297    ///
298    /// - Precondition: The candidate and dependencies match their prepared storage types.
299    /// # Errors
300    /// Propagates operation failures.
301    /// - Complexity: O(a) pointer binding plus body costs.
302    pub(crate) fn call_with_prefix(
303        &mut self,
304        value: &dyn Any,
305        inputs: &[&dyn Any],
306    ) -> anyhow::Result<OwnedValue> {
307        self.call_arguments(
308            std::iter::once(value).chain(inputs.iter().copied()),
309            inputs.len() + 1,
310        )
311    }
312
313    /// Checks every erased input before binding any address or executing the prefixed frame.
314    ///
315    /// # Errors
316    /// Rejects missing or extra inputs, wrong scalar identities, compound carriers, or
317    /// recursive shapes on every invocation; propagates operation failures after cleanup.
318    /// Rejection leaves previously bound pointers unused and permits later valid calls.
319    /// - Complexity: O(a + n) validation for a inputs and n compound descriptor nodes,
320    ///   plus pointer binding and body costs.
321    pub(crate) fn call_checked_with_prefix(
322        &mut self,
323        value: &dyn Any,
324        inputs: &[&dyn Any],
325    ) -> anyhow::Result<OwnedValue> {
326        anyhow::ensure!(
327            inputs.len().checked_add(1) == Some(self.inputs.len()),
328            "Adam argument count mismatch"
329        );
330        for (input, expected) in std::iter::once(value)
331            .chain(inputs.iter().copied())
332            .zip(&self.inputs)
333        {
334            expected.validate(input)?;
335        }
336        self.call_with_prefix(value, inputs)
337    }
338
339    /// Binds borrowed host addresses into the reusable canonical pointer slots.
340    ///
341    /// - Precondition: Input count and types match preparation.
342    /// # Errors
343    /// Propagates operation failures after raw frame cleanup.
344    /// - Complexity: O(a) pointer binding plus body costs.
345    fn call_arguments<'a>(
346        &mut self,
347        inputs: impl Iterator<Item = &'a dyn Any>,
348        arity: usize,
349    ) -> anyhow::Result<OwnedValue> {
350        debug_assert_eq!(arity, self.pointers.len());
351        for ((input, adapter), pointer) in inputs.zip(&self.inputs).zip(&mut self.pointers) {
352            let data = std::ptr::from_ref(input).cast::<()>();
353            *pointer = match adapter {
354                Input::Compound(_) => {
355                    // SAFETY: Adam's internal boundary or the checked public
356                    // adapter verifies the private carrier and complete shape.
357                    unsafe { &*data.cast::<CellValue>() }.0.as_ptr()
358                }
359                Input::Leaf(_) => data.cast(),
360            };
361        }
362        // SAFETY: Construction validates every read. The internal callback boundary
363        // or checked entry point guarantees storage types and complete compound shapes.
364        unsafe { self.body.call(&self.pointers) }
365    }
366}
367
368/// Flattens a declared cell's `TypeShape` to the single `TypeId` `adam_rs` itself needs for its
369/// own (CEL-agnostic) storage bookkeeping. Compound shape identity remains separate from
370/// the private common storage carrier and is validated before constructing any adapter.
371pub(crate) fn cell_type_id(shape: &TypeShape) -> TypeId {
372    match shape {
373        TypeShape::Named(type_id) => *type_id,
374        _ => TypeId::of::<CellValue>(),
375    }
376}
377
378/// Prepares a numeric range filter shared by parsed and native builders.
379///
380/// - Precondition: All named shapes are registered.
381/// # Errors
382/// Rejects missing/mismatched numeric ranges, unavailable defaults or invalid frames.
383/// - Complexity: O(n + e + i) descriptor nodes, reads and inputs.
384pub(crate) fn build_range_filter(
385    body: DynSegment,
386    ids: Vec<CellId>,
387    inputs: &[TypeShape],
388    output: &TypeShape,
389    types: &TypeRegistry,
390) -> anyhow::Result<adam_rs::Filter> {
391    let id = body
392        .peek_output_type_id()
393        .ok_or_else(|| anyhow::anyhow!("filter has no result"))?;
394    let range = types
395        .range_entry(id)
396        .ok_or_else(|| anyhow::anyhow!("expected numeric range filter"))?;
397    let value_type = cell_type_id(output);
398    anyhow::ensure!(
399        range.element_type_id == value_type,
400        "filter range element mismatch"
401    );
402    let default = types
403        .entry_by_type_id(value_type)
404        .and_then(|entry| entry.default_fn)
405        .ok_or_else(|| anyhow::anyhow!("numeric range filter requires a default"))?;
406    let placeholder = default();
407    let result = body.peek_stack_infos(1)[0].value_type.clone();
408    let shapes: Vec<_> = std::iter::once(output.clone())
409        .chain(inputs.iter().cloned())
410        .collect();
411    let frame = std::rc::Rc::new(RefCell::new(RawFrame::new(body, &shapes, &result, types)?));
412    let clamp = frame.clone();
413    let clamp_fn = range.clamp_fn;
414    let bounds_fn = range.bounds_fn;
415    Ok(adam_rs::Filter::range(
416        value_type,
417        ids,
418        inputs.iter().map(cell_type_id).collect(),
419        move |value, args| clamp_fn(clamp.borrow_mut().call_with_prefix(value, args)?, value),
420        move |args| {
421            bounds_fn(
422                frame
423                    .borrow_mut()
424                    .call_checked_with_prefix(placeholder.as_ref(), args)
425                    .ok()?,
426            )
427            .ok()
428        },
429    ))
430}
431
432/// Prepares the common value-returning filter frame for parsed and native builders.
433///
434/// - Precondition: All named shapes are registered.
435/// # Errors
436/// Rejects incompatible emitted signatures or missing required cell capabilities.
437/// - Complexity: O(n + e + i) descriptor nodes, reads and inputs.
438pub(crate) fn build_value_filter(
439    body: DynSegment,
440    ids: Vec<CellId>,
441    inputs: &[TypeShape],
442    output: &TypeShape,
443    types: &TypeRegistry,
444) -> anyhow::Result<adam_rs::Filter> {
445    let shapes: Vec<_> = std::iter::once(output.clone())
446        .chain(inputs.iter().cloned())
447        .collect();
448    let frame = RefCell::new(Frame::new(
449        body,
450        &shapes,
451        std::slice::from_ref(output),
452        false,
453        types,
454    )?);
455    Ok(adam_rs::Filter::new(
456        cell_type_id(output),
457        ids,
458        inputs.iter().map(cell_type_id).collect(),
459        move |value, args| Ok(frame.borrow_mut().call_with_prefix(value, args)?.remove(0)),
460    ))
461}
462
463/// Prepares the common bool-returning requirement frame for both builder paths.
464///
465/// - Precondition: All named shapes are registered.
466/// # Errors
467/// Rejects incompatible signatures or unavailable borrowed cloning.
468/// - Complexity: O(n + e + i) descriptor nodes, reads and inputs.
469pub(crate) fn build_requirement(
470    body: DynSegment,
471    ids: Vec<CellId>,
472    inputs: &[TypeShape],
473    types: &TypeRegistry,
474) -> anyhow::Result<Requirement> {
475    let frame = RefCell::new(RawFrame::new(
476        body,
477        inputs,
478        &cel_runtime::ValueType::leaf::<bool>(),
479        types,
480    )?);
481    Ok(Requirement::new(
482        ids,
483        inputs.iter().map(cell_type_id).collect(),
484        move |args| {
485            let value = frame.borrow_mut().call(args)?;
486            // SAFETY: RawFrame bound the result to the complete concrete bool descriptor.
487            Ok(unsafe { value.into_typed_bound::<cel_runtime::Leaf<bool>>() })
488        },
489    ))
490}