Skip to main content

cel_parser/
registered_ops.rs

1//! Persistent operation and record metadata shared by independent host libraries.
2//!
3//! Register a function marker once, then add disjoint operand signatures from any
4//! installer. Ordinary calls and direct member lookup share the same emitters:
5//!
6//! ```
7//! use cel_parser::{OpLookup, OperationSignature, ResolvedLeafType};
8//! use cel_runtime::ValueType;
9//! #[derive(Clone)]
10//! struct Identity;
11//! let mut lookup = OpLookup::new();
12//! lookup.register_function("identity", Identity).unwrap();
13//! let ty = ResolvedLeafType::new("u32", ValueType::leaf::<u32>());
14//! lookup.register_operation(OperationSignature {
15//!     name: "identity", operands: vec![ty.clone().into()], result: ty.into(),
16//!     emit: Box::new(|segment, _| segment.op1(|value: u32| value)),
17//! }).unwrap();
18//! assert_eq!(lookup.operation_signatures("identity").count(), 1);
19//! ```
20
21use std::any::TypeId;
22
23use anyhow::{Result, ensure};
24use cel_runtime::{DynSegment, ValueType};
25
26use crate::{ExprSpan, OpLookup, ParseError, ResolvedLeafType, ResolvedType, SourceSpan};
27
28/// Emits an operation after the complete operand signature has matched.
29///
30/// - Precondition: the stack ends with the declared operand descriptors.
31/// - Postcondition: success replaces those operands with the declared result.
32///
33/// Emitters validate fallible setup before changing the segment. The lookup uniquely owns
34/// each emitter and invokes it only while compiling; segments own the emitted operations.
35/// Boxing a zero-sized, captureless closure does not allocate.
36pub type OperationEmitter = Box<dyn Fn(&mut DynSegment, SourceSpan) -> Result<()> + Send + Sync>;
37
38/// Consumes source-order fields using source-position to schema-position indices,
39/// the ambient stack byte offset, and the expression span.
40///
41/// The lookup uniquely owns this compile-time callback. Boxing a zero-sized,
42/// captureless closure does not allocate.
43pub type RecordEmitter =
44    Box<dyn Fn(&mut DynSegment, &[usize], usize, SourceSpan) -> Result<()> + Send + Sync>;
45
46/// Describes one named operation with complete concrete operand and result identities.
47///
48/// The emitter consumes exactly `operands` and produces exactly `result`.
49pub struct OperationSignature {
50    /// The shared operation name.
51    pub name: &'static str,
52    /// Operands in source order, including the receiver for member lookup.
53    pub operands: Vec<ResolvedType>,
54    /// The single result's complete type.
55    pub result: ResolvedType,
56    /// The implementation used by both direct lookup and ordinary calls.
57    pub emit: OperationEmitter,
58}
59
60/// Describes one required record field.
61pub struct RecordField {
62    /// The source field name.
63    pub name: &'static str,
64    /// The field's complete concrete type.
65    pub ty: ResolvedType,
66}
67
68/// Describes a named record's required fields and construction implementation.
69pub struct RecordSchema {
70    /// The constructed type, whose source name keys this schema.
71    pub ty: ResolvedLeafType,
72    /// Required fields in canonical constructor order.
73    pub fields: Vec<RecordField>,
74    /// The constructor, invoked only after field validation.
75    pub emit: RecordEmitter,
76}
77
78/// Stores the first marker selected for a function name.
79pub(crate) struct RegisteredFunction {
80    pub(crate) name: &'static str,
81    pub(crate) marker: ResolvedLeafType,
82    pub(crate) emit: OperationEmitter,
83}
84
85/// Holds permanent registrations independently of transient parser scopes.
86#[derive(Default)]
87pub(crate) struct RegisteredOps {
88    operations: Vec<OperationSignature>,
89    functions: Vec<RegisteredFunction>,
90    marker_names: Vec<(TypeId, &'static str)>,
91    records: Vec<RecordSchema>,
92}
93
94/// Compares concrete recursive identities without treating diagnostic names as identity.
95///
96/// - Complexity: O(n) in recursive descriptor nodes.
97fn same_type(left: &ResolvedType, right: &ResolvedType) -> bool {
98    match (left.complete_value_type(), right.complete_value_type()) {
99        (Ok(left), Ok(right)) => left.same_shape(&right),
100        _ => false,
101    }
102}
103
104/// Matches the complete runtime descriptor, never just an erased array marker.
105///
106/// - Complexity: O(n) in recursive descriptor nodes.
107fn matches_type(expected: &ResolvedType, actual: &ValueType) -> bool {
108    expected
109        .complete_value_type()
110        .is_ok_and(|expected| expected.same_shape(actual))
111}
112
113impl OpLookup {
114    /// Returns the concrete marker emitted by a bare registered function name.
115    ///
116    /// - Complexity: O(f) for f registered functions.
117    pub(crate) fn registered_function_type(&self, name: &str) -> Option<&ResolvedLeafType> {
118        self.registered
119            .functions
120            .iter()
121            .find(|function| function.name == name)
122            .map(|function| &function.marker)
123    }
124
125    /// Returns the callable name selected by executable dispatch for a concrete marker.
126    ///
127    /// - Complexity: O(f) for f registered functions.
128    pub(crate) fn registered_function_name(&self, marker: TypeId) -> Option<&'static str> {
129        self.registered
130            .functions
131            .iter()
132            .find(|function| function.marker.type_id() == marker)
133            .map(|function| function.name)
134    }
135
136    /// Validates required named fields and emits one record from their original stack order.
137    ///
138    /// Field descriptors must match exactly, including recursive array identities. Validation
139    /// never changes the segment; only a fully validated constructor invokes its emitter.
140    ///
141    /// - Precondition: the stack ends with one value per field, starting at `ambient_start`.
142    /// - Complexity: O(r + f² + f * d) for r schemas, f fields and descriptor depth d.
143    ///
144    /// # Errors
145    /// Returns an error for unknown records, unknown/duplicate/missing fields, mismatched
146    /// field types, or a constructor emitter's setup failure.
147    ///
148    /// # Examples
149    /// ```
150    /// use cel_parser::{CELParser, OpLookup, RecordSchema, ResolvedLeafType};
151    /// use cel_runtime::ValueType;
152    /// let mut lookup = OpLookup::new();
153    /// lookup.register_record(RecordSchema {
154    ///     ty: ResolvedLeafType::new("Empty", ValueType::leaf::<()>()),
155    ///     fields: vec![],
156    ///     emit: Box::new(|s, _, _, _| { s.just(()); Ok(()) }),
157    /// }).unwrap();
158    /// CELParser::new(lookup).parse_str("Empty {}").unwrap().call0::<()>().unwrap();
159    /// ```
160    pub fn construct_record(
161        &self,
162        name: &str,
163        fields: &[(String, ExprSpan)],
164        ambient_start: usize,
165        segment: &mut DynSegment,
166        start: proc_macro2::Span,
167        end: proc_macro2::Span,
168    ) -> crate::Result<()> {
169        let error = |message| ParseError::new_range(message, start, end);
170        let schema = self
171            .record_schema(name)
172            .ok_or_else(|| error(format!("unknown record `{name}`")))?;
173        let mut mapping = Vec::with_capacity(fields.len());
174        for (index, (field, span)) in fields.iter().enumerate() {
175            let schema_index = schema
176                .fields
177                .iter()
178                .position(|candidate| candidate.name == field)
179                .ok_or_else(|| {
180                    ParseError::new_range(
181                        format!("unknown field `{field}` in record `{name}`"),
182                        span.start,
183                        span.end,
184                    )
185                })?;
186            if fields[..index]
187                .iter()
188                .any(|(previous, _)| previous == field)
189            {
190                return Err(ParseError::new_range(
191                    format!("duplicate field `{field}` in record `{name}`"),
192                    span.start,
193                    span.end,
194                ));
195            }
196            mapping.push(schema_index);
197        }
198        if let Some(missing) = schema
199            .fields
200            .iter()
201            .enumerate()
202            .find(|(index, _)| !mapping.contains(index))
203        {
204            return Err(error(format!(
205                "missing field `{}` in record `{name}`",
206                missing.1.name
207            )));
208        }
209        let operands = segment.peek_stack_infos(fields.len());
210        debug_assert_eq!(operands.len(), fields.len());
211        debug_assert_eq!(
212            segment.stack_offset_before_top(fields.len()).ok(),
213            Some(ambient_start)
214        );
215        for ((actual, &schema_index), (field, span)) in operands.iter().zip(&mapping).zip(fields) {
216            let expected = &schema.fields[schema_index].ty;
217            if !matches_type(expected, &actual.value_type) {
218                return Err(ParseError::new_range(
219                    format!(
220                        "field `{field}`: type mismatch: expected `{}`",
221                        expected.display_name()
222                    ),
223                    span.start,
224                    span.end,
225                ));
226            }
227        }
228        (schema.emit)(
229            segment,
230            &mapping,
231            ambient_start,
232            SourceSpan::from_proc_macro2_range(start, end),
233        )
234        .map_err(|err| error(err.to_string()))?;
235        segment
236            .annotate_top(
237                schema
238                    .ty
239                    .complete_value_type()
240                    .map_err(|err| error(err.to_string()))?
241                    .clone(),
242            )
243            .map_err(|err| error(err.to_string()))
244    }
245
246    /// Registers one persistent named operation without replacing existing overloads.
247    ///
248    /// # Errors
249    /// Returns an error for unavailable complete factories or unrepresentable layouts, and
250    /// duplicate operand identities, including return-type-only overloads.
251    ///
252    /// - Complexity: O(s * a * d) for s signatures, a operands and descriptor depth d.
253    ///
254    /// # Examples
255    /// See the [registration tutorial](crate::registered_ops).
256    pub fn register_operation(&mut self, signature: OperationSignature) -> Result<()> {
257        for operand in &signature.operands {
258            operand.complete_value_type()?;
259        }
260        signature.result.complete_value_type()?;
261        ensure!(
262            !self.operation_signatures(signature.name).any(|existing| {
263                existing.operands.len() == signature.operands.len()
264                    && existing
265                        .operands
266                        .iter()
267                        .zip(&signature.operands)
268                        .all(|(a, b)| same_type(a, b))
269            }),
270            "duplicate operation signature for `{}`",
271            signature.name
272        );
273        self.registered.operations.push(signature);
274        Ok(())
275    }
276
277    /// Binds a persistent callable name, retaining its first marker across installers.
278    ///
279    /// Markers require `Send + Sync` so lookup retains its thread-safety guarantees.
280    ///
281    /// # Errors
282    /// Returns an error if this marker type has already named a distinct function.
283    ///
284    /// - Complexity: O(f) for f registered marker types.
285    ///
286    /// # Examples
287    /// See the [registration tutorial](crate::registered_ops).
288    pub fn register_function<M: Clone + Send + Sync + 'static>(
289        &mut self,
290        name: &'static str,
291        marker: M,
292    ) -> Result<()> {
293        let marker_type = TypeId::of::<M>();
294        ensure!(
295            self.registered
296                .marker_names
297                .iter()
298                .all(|(ty, bound_name)| *ty != marker_type || *bound_name == name),
299            "function marker already names another function"
300        );
301        if !self.registered.marker_names.contains(&(marker_type, name)) {
302            self.registered.marker_names.push((marker_type, name));
303        }
304        if !self
305            .registered
306            .functions
307            .iter()
308            .any(|function| function.name == name)
309        {
310            self.registered.functions.push(RegisteredFunction {
311                name,
312                marker: ResolvedLeafType::new(name, ValueType::cloneable_leaf::<M>()),
313                emit: Box::new(move |segment, _| {
314                    segment.just(marker.clone());
315                    Ok(())
316                }),
317            });
318        }
319        Ok(())
320    }
321
322    /// Registers a persistent record schema keyed by its source type name.
323    ///
324    /// # Errors
325    /// Returns an error for unavailable complete factories or unrepresentable layouts, a
326    /// duplicate schema name, or duplicate field names.
327    ///
328    /// - Complexity: O(r + f²) for r schemas and f fields.
329    ///
330    /// # Examples
331    /// ```
332    /// use cel_parser::{OpLookup, RecordSchema, ResolvedLeafType};
333    /// use cel_runtime::ValueType;
334    /// let mut lookup = OpLookup::new();
335    /// lookup.register_record(RecordSchema {
336    ///     ty: ResolvedLeafType::new("Unit", ValueType::leaf::<()>()),
337    ///     fields: vec![],
338    ///     emit: Box::new(|segment, _, _, _| { segment.just(()); Ok(()) }),
339    /// }).unwrap();
340    /// assert!(lookup.record_schema("Unit").is_some());
341    /// ```
342    pub fn register_record(&mut self, schema: RecordSchema) -> Result<()> {
343        schema.ty.complete_value_type()?;
344        for field in &schema.fields {
345            field.ty.complete_value_type()?;
346        }
347        ensure!(
348            self.record_schema(schema.ty.type_name()).is_none(),
349            "duplicate record schema `{}`",
350            schema.ty.type_name()
351        );
352        for (index, field) in schema.fields.iter().enumerate() {
353            ensure!(
354                !schema.fields[..index]
355                    .iter()
356                    .any(|other| other.name == field.name),
357                "duplicate record field `{}`",
358                field.name
359            );
360        }
361        self.registered.records.push(schema);
362        Ok(())
363    }
364
365    /// Iterates persistent signatures named `name` in registration order.
366    ///
367    /// - Complexity: O(s) to exhaust s registered signatures.
368    ///
369    /// # Examples
370    /// See the [registration tutorial](crate::registered_ops).
371    pub fn operation_signatures(&self, name: &str) -> impl Iterator<Item = &OperationSignature> {
372        self.registered
373            .operations
374            .iter()
375            .filter(move |signature| signature.name == name)
376    }
377
378    /// Returns the persistent schema with the given source type name.
379    ///
380    /// - Complexity: O(r) for r registered schemas.
381    ///
382    /// # Examples
383    /// See [`Self::register_record`].
384    pub fn record_schema(&self, name: &str) -> Option<&RecordSchema> {
385        self.registered
386            .records
387            .iter()
388            .find(|schema| schema.ty.type_name() == name)
389    }
390
391    /// Dispatches a registered signature only after every operand descriptor matches.
392    ///
393    /// Declining a signature leaves operand descriptors and emitted operations unchanged.
394    ///
395    /// # Errors
396    /// Returns the selected emitter's setup error.
397    ///
398    /// - Complexity: O(s * a * d) for s signatures, a operands and descriptor depth d.
399    pub(crate) fn lookup_registered_operation(
400        &self,
401        name: &str,
402        segment: &mut DynSegment,
403        arity: usize,
404        span: SourceSpan,
405    ) -> Result<bool> {
406        let operands = segment.peek_stack_infos(arity);
407        if operands.len() != arity {
408            return Ok(false);
409        }
410        for signature in self.operation_signatures(name) {
411            if signature.operands.len() == arity
412                && signature
413                    .operands
414                    .iter()
415                    .zip(operands)
416                    .all(|(expected, actual)| matches_type(expected, &actual.value_type))
417            {
418                (signature.emit)(segment, span)?;
419                segment.annotate_top(signature.result.complete_value_type()?.clone())?;
420                return Ok(true);
421            }
422        }
423        Ok(false)
424    }
425
426    /// Resolves a bare registered name or a marker-qualified ordinary call.
427    ///
428    /// # Errors
429    /// Returns a selected scope/emitter error or a checked stack-offset error.
430    ///
431    /// - Complexity: O(f + k + s * a * d + n) for f functions, k scopes,
432    ///   s signatures, a operands, descriptor depth d and n ambient stack entries.
433    pub(crate) fn lookup_registered(
434        &self,
435        name: &str,
436        segment: &mut DynSegment,
437        arity: usize,
438        span: SourceSpan,
439    ) -> Result<bool> {
440        if arity == 0
441            && let Some(function) = self.registered.functions.iter().find(|f| f.name == name)
442        {
443            (function.emit)(segment, span)?;
444            return Ok(true);
445        }
446        if name == "()" && arity > 0 {
447            let operands = segment.peek_stack_infos(arity);
448            if operands.len() != arity {
449                return Ok(false);
450            }
451            let marker_type = operands[0].value_type.type_id();
452            if let Some(function) = self
453                .registered
454                .functions
455                .iter()
456                .find(|f| f.marker.type_id() == marker_type)
457            {
458                let ambient_start = segment.stack_offset_before_top(arity)?;
459                if self.lookup_direct_operation(function.name, segment, arity - 1, span)? {
460                    segment.make_tuple(2, ambient_start)?;
461                    segment.tuple_index(1)?;
462                    return Ok(true);
463                }
464                return Ok(false);
465            }
466        }
467        self.lookup_registered_operation(name, segment, arity, span)
468    }
469}