Skip to main content

OpLookup

Struct OpLookup 

Source
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

Source

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();
Source

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.

Source

pub fn push_scope<F>(&mut self, scope: F)
where F: Fn(&str, &mut DynSegment, usize, SourceSpan) -> Result<bool> + Send + Sync + 'static,

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.

Source

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.
Source

pub fn push_library_scope<F>(&mut self, scope: F)
where F: Fn(&str, &mut DynSegment, usize, SourceSpan) -> Result<bool> + Send + Sync + 'static,

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).
Source

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_scopes is 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());
Source

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: scopes came from a matching isolate_scopes() call on this same OpLookup — restoring an arbitrary Vec<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 again
Source

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());
Source

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);
Source

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 segment that 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

Source

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();
Source

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.

Source

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.

Source

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());
Source

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.

Source

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.
§Examples

See Self::register_record.

Trait Implementations§

Source§

impl Default for OpLookup

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.