pub struct OpLookup { /* private fields */ }Expand description
Operation lookup with scope stack support.
Resolves scopes in LIFO order, then persistent named registrations, tuple signatures, and built-in operations. Named registrations survive scope isolation.
§Examples
use cel_parser::op_table::OpLookup;
use cel_runtime::DynSegment;
use std::any::TypeId;
let mut lookup = OpLookup::new();
// Use built-in addition
let mut segment = DynSegment::new::<()>();
segment.just(10u32);
segment.just(20u32);
lookup.lookup("+", &mut segment, 2, proc_macro2::Span::call_site(), proc_macro2::Span::call_site()).unwrap();
assert_eq!(segment.call0::<u32>().unwrap(), 30);Implementations§
Source§impl OpLookup
impl OpLookup
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new operation lookup with only built-in operations - the
infix/prefix operators, the cast operator (as), and range syntax.
§Examples
use cel_parser::OpLookup;
let lookup = OpLookup::new();Sourcepub fn register_tuple_op(&mut self, signature: TupleOpSignature)
pub fn register_tuple_op(&mut self, signature: TupleOpSignature)
Registers a tuple-shaped operator signature, matched by element
TypeId sequence the same way built-in operators are matched by flat
TypeId.
Sourcepub fn push_scope<F>(&mut self, scope: F)
pub fn push_scope<F>(&mut self, scope: F)
Pushes a new scope onto the stack.
Accepts a closure directly; it is boxed internally. The scope should return
Ok(true) if it handled the operation, Ok(false) to pass to the next scope,
or Err on error. Error messages surface verbatim; they should be lowercase, end
without a period, and wrap identifiers and type names in backticks.
Sourcepub fn pop_scope(&mut self) -> Option<ScopeFn>
pub fn pop_scope(&mut self) -> Option<ScopeFn>
Pops the most recent scope from the stack.
Returns the popped scope, or None if the stack is empty.
- Precondition: must not remove a library scope registered via
push_library_scope— those are permanent setup-time registrations that must survive across multiple parses.
Sourcepub fn push_library_scope<F>(&mut self, scope: F)
pub fn push_library_scope<F>(&mut self, scope: F)
Registers a permanent, library-level scope that is reachable from every parse, including inside closure bodies.
Used for built-in language features and statically-installed library
functions (like round and clamp from a cel-std-style crate). These scopes are registered
once at setup time and must always be available, even when isolate_scopes
is active — library scopes are never isolated.
Do not use for scopes tied to a single parse’s lifetime — use push_scope
for those, which can be isolated during nested body compilation (closures).
- Complexity: O(1).
Sourcepub fn isolate_scopes(&mut self) -> Vec<ScopeFn> ⓘ
pub fn isolate_scopes(&mut self) -> Vec<ScopeFn> ⓘ
Temporarily removes every transient scope (those pushed via push_scope),
returning them so a later restore_scopes call can put them back.
Library scopes registered via push_library_scope are never
isolated and remain reachable.
Used when compiling an independent nested body (a closure literal) that must resolve names
against only its own declared parameters and library functions (like round, clamp) —
never whatever transient per-parse scopes happen to be active. This maintains the invariant
that library functions are always available, including inside closures, while per-declaration
scopes (which tie to a single outer parse’s lifetime) are hidden from nested bodies.
- Postcondition: library scopes remain reachable; transient scopes are inaccessible until
restore_scopesis called.
§Examples
use cel_parser::OpLookup;
use cel_runtime::DynSegment;
let mut lookup = OpLookup::new();
lookup.push_scope(|name, segment, arity, _span| {
if name == "custom" && arity == 0 {
segment.just(42i32);
Ok(true)
} else {
Ok(false)
}
});
let saved = lookup.isolate_scopes();
// Now the custom scope is inaccessible
let mut segment = DynSegment::new::<()>();
let result = lookup.lookup("custom", &mut segment, 0, proc_macro2::Span::call_site(), proc_macro2::Span::call_site());
assert!(result.is_err());
lookup.restore_scopes(saved);
// Now the custom scope is reachable again
let mut segment = DynSegment::new::<()>();
let result = lookup.lookup("custom", &mut segment, 0, proc_macro2::Span::call_site(), proc_macro2::Span::call_site());
assert!(result.is_ok());Sourcepub fn restore_scopes(&mut self, scopes: Vec<ScopeFn>)
pub fn restore_scopes(&mut self, scopes: Vec<ScopeFn>)
Restores a scope stack previously removed by isolate_scopes,
discarding whatever scopes were pushed while isolated.
Library scopes (those registered via push_library_scope)
are unaffected by isolation and restoration — they persist across the entire operation.
- Precondition:
scopescame from a matchingisolate_scopes()call on this sameOpLookup— restoring an arbitraryVec<ScopeFn>is well-typed but not a meaningful use of this method.
§Examples
use cel_parser::OpLookup;
use cel_runtime::DynSegment;
let mut lookup = OpLookup::new();
lookup.push_scope(|name, segment, arity, _span| {
if name == "outer" && arity == 0 {
segment.just(100i32);
Ok(true)
} else {
Ok(false)
}
});
let saved = lookup.isolate_scopes();
lookup.restore_scopes(saved);
// The outer scope is now reachable againSourcepub fn lookup(
&self,
name: &str,
segment: &mut DynSegment,
num_operands: usize,
start: Span,
end: Span,
) -> Result<(), ParseError>
pub fn lookup( &self, name: &str, segment: &mut DynSegment, num_operands: usize, start: Span, end: Span, ) -> Result<(), ParseError>
Looks up and applies an operation, attaching the expression span to any error.
Searches scopes in LIFO order, then persistent named registrations, tuple signatures, and built-in operations.
§Errors
Returns a crate::ParseError spanning start..=end if no scope or built-in
handles the request, or if a scope itself returns an error.
- Complexity: O(k) in the number of registered scopes, plus O(s) for the built-in signature scan where s is the number of signatures for the operator.
§Examples
use proc_macro2::Span;
use cel_parser::OpLookup;
use cel_runtime::DynSegment;
let lookup = OpLookup::new();
let mut seg = DynSegment::new::<()>();
// A lookup with zero operands for a known operator succeeds when types match.
// This example shows the signature only; real usage requires pushed types.
let result = lookup.lookup("+", &mut seg, 2, Span::call_site(), Span::call_site());
// result is Err because no operands are on the segment
assert!(result.is_err());Sourcepub fn lookup_member(
&self,
name: &str,
segment: &mut DynSegment,
arity: usize,
start: Span,
end: Span,
) -> Result<()>
pub fn lookup_member( &self, name: &str, segment: &mut DynSegment, arity: usize, start: Span, end: Span, ) -> Result<()>
Applies a direct named operation without looking up a zero-arity value binding.
Scopes retain LIFO precedence for nonzero operand counts; registered operations, tuple signatures and built-ins provide the fallbacks. A declined signature leaves the stack unchanged.
§Errors
Returns a spanned error if no operation matches or an emitter fails.
- Complexity: O(k + s * a * d) for k scopes, s signatures, a operands and descriptor depth d.
§Examples
use cel_parser::OpLookup;
use cel_runtime::DynSegment;
use proc_macro2::Span;
let mut segment = DynSegment::new::<()>();
segment.just(1u32);
segment.just(2u32);
OpLookup::new().lookup_member("+", &mut segment, 2,
Span::call_site(), Span::call_site()).unwrap();
assert_eq!(segment.call0::<u32>().unwrap(), 3);Sourcepub fn lookup_cast(
&self,
type_name: &str,
segment: &mut DynSegment,
start: Span,
end: Span,
) -> Result<(), ParseError>
pub fn lookup_cast( &self, type_name: &str, segment: &mut DynSegment, start: Span, end: Span, ) -> Result<(), ParseError>
Looks up and applies a cast (expr as Type), attaching the expression span to any error.
- Postcondition: on success, an operation is queued onto
segmentthat replaces its top-of-stack operand with the converted value once the segment is run (e.g. via [DynSegment::call0]) - like every other op this crate queues rather than executes immediately.
§Errors
Returns a crate::ParseError spanning start..=end if segment’s stack is empty, if
type_name isn’t a recognized cast-target type, or if no cast from the operand’s type to
it is registered. A registered conversion’s own value-range failure (e.g. an out-of-range
or non-finite value) is deferred to execution - it surfaces from running the segment, not
from this function (see cast_errors_when_the_value_does_not_fit_in_the_target_type
below).
- Complexity: O(s) in the number of registered sources for
type_name.
§Examples
use proc_macro2::Span;
use cel_parser::OpLookup;
use cel_runtime::DynSegment;
let lookup = OpLookup::new();
let mut seg = DynSegment::new::<()>();
seg.just(1024i32);
lookup
.lookup_cast("f64", &mut seg, Span::call_site(), Span::call_site())
.unwrap();
assert_eq!(seg.call0::<f64>().unwrap(), 1024.0);Source§impl OpLookup
impl OpLookup
Sourcepub fn construct_record(
&self,
name: &str,
fields: &[(String, ExprSpan)],
ambient_start: usize,
segment: &mut DynSegment,
start: Span,
end: Span,
) -> Result<()>
pub fn construct_record( &self, name: &str, fields: &[(String, ExprSpan)], ambient_start: usize, segment: &mut DynSegment, start: Span, end: Span, ) -> Result<()>
Validates required named fields and emits one record from their original stack order.
Field descriptors must match exactly, including recursive array identities. Validation never changes the segment; only a fully validated constructor invokes its emitter.
- Precondition: the stack ends with one value per field, starting at
ambient_start. - Complexity: O(r + f² + f * d) for r schemas, f fields and descriptor depth d.
§Errors
Returns an error for unknown records, unknown/duplicate/missing fields, mismatched field types, or a constructor emitter’s setup failure.
§Examples
use cel_parser::{CELParser, OpLookup, RecordSchema, ResolvedLeafType};
use cel_runtime::ValueType;
let mut lookup = OpLookup::new();
lookup.register_record(RecordSchema {
ty: ResolvedLeafType::new("Empty", ValueType::leaf::<()>()),
fields: vec![],
emit: Box::new(|s, _, _, _| { s.just(()); Ok(()) }),
}).unwrap();
CELParser::new(lookup).parse_str("Empty {}").unwrap().call0::<()>().unwrap();Sourcepub fn register_operation(
&mut self,
signature: OperationSignature,
) -> Result<()>
pub fn register_operation( &mut self, signature: OperationSignature, ) -> Result<()>
Registers one persistent named operation without replacing existing overloads.
§Errors
Returns an error for unavailable complete factories or unrepresentable layouts, and duplicate operand identities, including return-type-only overloads.
- Complexity: O(s * a * d) for s signatures, a operands and descriptor depth d.
§Examples
See the registration tutorial.
Sourcepub fn register_function<M: Clone + Send + Sync + 'static>(
&mut self,
name: &'static str,
marker: M,
) -> Result<()>
pub fn register_function<M: Clone + Send + Sync + 'static>( &mut self, name: &'static str, marker: M, ) -> Result<()>
Binds a persistent callable name, retaining its first marker across installers.
Markers require Send + Sync so lookup retains its thread-safety guarantees.
§Errors
Returns an error if this marker type has already named a distinct function.
- Complexity: O(f) for f registered marker types.
§Examples
See the registration tutorial.
Sourcepub fn register_record(&mut self, schema: RecordSchema) -> Result<()>
pub fn register_record(&mut self, schema: RecordSchema) -> Result<()>
Registers a persistent record schema keyed by its source type name.
§Errors
Returns an error for unavailable complete factories or unrepresentable layouts, a duplicate schema name, or duplicate field names.
- Complexity: O(r + f²) for r schemas and f fields.
§Examples
use cel_parser::{OpLookup, RecordSchema, ResolvedLeafType};
use cel_runtime::ValueType;
let mut lookup = OpLookup::new();
lookup.register_record(RecordSchema {
ty: ResolvedLeafType::new("Unit", ValueType::leaf::<()>()),
fields: vec![],
emit: Box::new(|segment, _, _, _| { segment.just(()); Ok(()) }),
}).unwrap();
assert!(lookup.record_schema("Unit").is_some());Sourcepub fn operation_signatures(
&self,
name: &str,
) -> impl Iterator<Item = &OperationSignature>
pub fn operation_signatures( &self, name: &str, ) -> impl Iterator<Item = &OperationSignature>
Iterates persistent signatures named name in registration order.
- Complexity: O(s) to exhaust s registered signatures.
§Examples
See the registration tutorial.
Sourcepub fn record_schema(&self, name: &str) -> Option<&RecordSchema>
pub fn record_schema(&self, name: &str) -> Option<&RecordSchema>
Returns the persistent schema with the given source type name.
- Complexity: O(r) for r registered schemas.