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}