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}