Skip to main content

Context

Struct Context 

Source
pub struct Context {
Show 16 fields pub root_node: Option<Arc<Node>>, pub decorators: HashMap<String, Arc<dyn DecoratorPlugin>>, pub functions: HashMap<String, GatedNativeFn>, pub native_methods: HashMap<String, HashMap<String, GatedNativeFn>>, pub schemas: HashMap<String, Value>, pub path_cache: Mutex<HashMap<String, Value>>, pub module_cache: Mutex<HashMap<String, Value>>, pub iter_cursors: Mutex<HashMap<u64, usize>>, pub iter_id_counter: AtomicU64, pub loading_modules: Mutex<HashMap<String, usize>>, pub evaluating_paths: Mutex<HashSet<String>>, pub step_counter: AtomicU64, pub closure_call_counter: AtomicU64, pub workspace: Option<Arc<WorkspaceTree>>, pub sandboxed_flag: bool, pub backend_prepared: bool, /* private fields */
}
Expand description

Shared execution environment for one or more evaluations.

Holds the document root, registered plugins, cached modules, and sandbox Capabilities. Thread-safe.

Most fields are pub so any backend implementing crate::Evaluator from a separate crate can read and update them. The sandbox-policy fields (capabilities, module_resolvers, analyzed) are private; hosts and backends go through the constructor / register_* / with_* helpers and the &-returning getters instead.

Fields§

§root_node: Option<Arc<Node>>§decorators: HashMap<String, Arc<dyn DecoratorPlugin>>§functions: HashMap<String, GatedNativeFn>§native_methods: HashMap<String, HashMap<String, GatedNativeFn>>

Schema-rooted Phase D: native methods registered against a specific schema. Keyed by (schema_name, method_name) so a host can attach register_method("Money", "cents_value", gate, func) and the evaluator dispatches m.cents_value() to it when m’s brand is "Money". Mirrors the analyzer’s tree.method_signatures shape; the #native directive on a with { ... } method declares the slot, the host fills it at runtime through this map. P2-4: nested map keyed schema -> method -> entry so per-call try_call_native_method looks up without minting a (String, String) tuple on every dispatch. The outer/inner HashMap::get(&str) paths borrow the schema/method names directly, eliminating the prior 2 × String::from allocations on every comparator / index / arithmetic dispatch.

§schemas: HashMap<String, Value>§path_cache: Mutex<HashMap<String, Value>>§module_cache: Mutex<HashMap<String, Value>>§iter_cursors: Mutex<HashMap<u64, usize>>

Backing cursor table for user-callable Iter.next(). Keyed by the u64 iter-id minted by Context::next_iter_id at the iter() call site and stamped into the resulting Iter-branded dict as _id. The Value graph is immutable (Arc-shared, no interior mutability), so cursor state must live outside it; this Context field is the canonical home — entries die when the Context is dropped, and the table is cleared at the start of every top-level eval_root / run_main so long-running hosts reusing a Context never accumulate stale cursors. Cross-Context Iter values surface as exhausted (next() returns None): see NativeFnCaps::iter_cursor_fetch_and_inc.

§iter_id_counter: AtomicU64

Monotonic per-Context id generator paired with Context::iter_cursors. Wraps at u64::MAX, effectively never reached in practice. Deliberately not reset on eval_root / run_main cleanup — the cursor table is, but the counter must keep climbing so a still-live Iter dict from the prior run can’t collide with a fresh one in the new run.

§loading_modules: Mutex<HashMap<String, usize>>

Modules currently on the load stack, with a re-entry counter so the same canonical id can appear multiple times (e.g. via as= vs spread=true) without the inner guard’s Drop clearing the outer frame’s record. Decrement on drop, remove when zero.

§evaluating_paths: Mutex<HashSet<String>>§step_counter: AtomicU64§closure_call_counter: AtomicU64

Monotonic counter incremented once per closure invocation. Used by eval_closure to derive a fresh cache_namespace for each call so that path-cache entries computed inside the closure body (e.g. &sibling.x) are not shared across distinct invocations with different bound parameters.

§workspace: Option<Arc<WorkspaceTree>>

Pre-computed workspace tree (entry + every reachable module), produced by relon_analyzer::analyze_entry. When present, the evaluator’s evaluate_module_source skips the per-module parse-plus-analyze pass and looks up the cached node and analyzed tree directly. The field is independent of analyzed; the latter remains the side-table for the entry file specifically, so existing callers that don’t drive workspace analysis keep working unchanged.

§sandboxed_flag: bool

Set by Context::sandboxed so the backend’s deferred setup step can attach the default-deny filesystem resolver after the stdlib / decorators / prelude registration. Untouched by the bare Context::new constructor.

§backend_prepared: bool

Tracks whether the backend has already installed its default stdlib / decorators / prelude into this context. Flipped from false to true by TreeWalkEvaluator::new (and any future backend) on first wrap, so a Context reused across multiple evaluator instances doesn’t pay the registration cost twice and a host re-registering an intrinsic isn’t silently undone on a second wrap.

Implementations§

Source§

impl Context

Source

pub fn new() -> Self

Construct a Context with no plugins / resolvers / stdlib pre-registered. Backend crates (e.g. relon-evaluator) attach their own stdlib + decorators + module resolvers when the host constructs an evaluator on top of this context (the tree-walking backend does this lazily in TreeWalkEvaluator::new so users keep the historical “call Context::new() then go” ergonomics).

Source

pub fn sandboxed() -> Self

Sandboxed counterpart to Self::new. The bare construction is identical; the only difference is sandboxed_flag = true, which the active backend reads when it installs its defaults so a default-deny filesystem resolver is appended after the standard std/... resolver. The tree-walking backend implements this hook in TreeWalkEvaluator::new.

Source

pub fn with_root(self, node: Node) -> Self

Source

pub fn with_analyzed(self, tree: Arc<AnalyzedTree>) -> Self

Source

pub fn with_workspace(self, workspace: Arc<WorkspaceTree>) -> Self

Wire a pre-computed workspace tree into the context. The workspace’s entry tree (if present) is also installed as analyzed so callers that read either field see consistent data — gives single-file consumers the same view they had before, and gives module-loading code a fast path to skip per-module parse + analyze.

Source

pub fn with_capabilities(self, capabilities: Capabilities) -> Self

Set the sandbox capability grants. Construction-time only by design: the method consumes self, so it composes with the other with_* builders but cannot retarget a context that is already shared with an evaluator (those hold Arc<Context>). There is deliberately no &mut self setter — widening a sandbox mid-run is not a supported operation.

Source

pub fn capabilities(&self) -> &Capabilities

Read-only view of the sandbox capability grants.

Source

pub fn analyzed(&self) -> Option<&Arc<AnalyzedTree>>

Read-only view of the analyzer side-table for the entry file, when one was installed via Self::with_analyzed / Self::with_workspace.

Source

pub fn module_resolvers(&self) -> &[Arc<dyn ModuleResolver>]

Read-only view of the module-resolution chain, in consultation order (front wins).

Source

pub fn prepend_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>)

Insert a resolver at the front of the chain so it is consulted before every existing resolver (front wins).

Source

pub fn append_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>)

Append a resolver at the back of the chain so it is consulted only when no earlier resolver claimed the path. This is where a backend installs catch-all / default-deny resolvers (e.g. the sandboxed filesystem resolver) during its prepare step.

Source

pub fn register_fn<S: Into<String>>( &mut self, name: S, gate: NativeFnGate, func: Arc<dyn RelonFunction>, )

Register a native function with explicit capability requirements. The function declares which bits it needs via gate; under the sandbox the call is rejected unless every set bit is granted in the context-wide Capabilities.

For pure functions (no host capability, no I/O, no ambient state) prefer Self::register_pure_fn — it makes the “this fn is pure” intent explicit. Passing NativeFnGate::default() here is equivalent.

Source

pub fn register_pure_fn<S: Into<String>>( &mut self, name: S, func: Arc<dyn RelonFunction>, )

Register a pure native function: no I/O, no ambient state, no host capability required. Equivalent to register_fn(name, NativeFnGate::default(), func). The all-zero gate is trivially satisfied by every Capabilities value, so pure fns keep working under a fully sandboxed context.

Stdlib intrinsics (len, range, string.*, …) and deterministic host fns whose contract is “args in, value out” register through this entry point.

Source

pub fn register_method<S: Into<String>, M: Into<String>>( &mut self, schema: S, method: M, gate: NativeFnGate, func: Arc<dyn RelonFunction>, )

Schema-rooted Phase D: attach a host-supplied implementation to a #native method on a specific schema. The evaluator dispatches value.method(...) to this fn whenever value’s brand matches schema and the source-side method body is absent (declared #native). Capability gating mirrors Self::register_fn: the gate declares which Capabilities bits the body needs at runtime, and a denied caller surfaces RuntimeError::CapabilityDenied.

Replaces the v1 pattern of register_fn("Schema.method", ...) with a key shape that tracks the schema-rooted dispatch model directly — no string concatenation, no shadowing of free fn names by accident.

Source

pub fn register_pure_method<S: Into<String>, M: Into<String>>( &mut self, schema: S, method: M, func: Arc<dyn RelonFunction>, )

Pure-method counterpart to Self::register_method. Equivalent to passing NativeFnGate::default (the all-zero gate) — the method body needs no host capability, so it dispatches under every Capabilities including the zero-trust default.

Source

pub fn register_decorator<S: Into<String>>( &mut self, name: S, plugin: Arc<dyn DecoratorPlugin>, )

Source

pub fn register_schema<S: Into<String>>(&mut self, name: S, schema: Value)

Source

pub fn enter_loading_module(&self, id: String) -> LoadingModuleGuard<'_>

Source

pub fn analyzer_target(&self, id: NodeId) -> Option<Node>

Source

pub fn next_iter_id(&self) -> u64

Mint a fresh Iter cursor id under this Context and seed a zero cursor entry so that subsequent Context::iter_cursor_fetch_and_inc calls can distinguish a “freshly minted, cursor at 0” iter from a foreign-Context iter (no entry → treated as exhausted; see policy note on iter_cursor_fetch_and_inc).

Each xs.iter() consumes one id; two Contexts mint independently because each owns its own counter. Wraps at u64::MAX — reachable only in pathological constructions — and the Relaxed ordering is sufficient because the id is opaque outside of Context::iter_cursors lookup.

Source

pub fn iter_cursor_fetch_and_inc( &self, iter_id: u64, len: usize, ) -> Option<usize>

Atomically read the cursor for iter_id, and if cursor < len, post-increment and return the old value; otherwise return None. A missing entry (no cursor was ever minted for iter_id in this Context) is also reported as None — idempotent end-of-iter, matching the Option::None return type of Iter.next() -> Option<T>.

Cross-Context policy (deliberate): if the host hands an Iter value built in Context A to Context B and then calls next(), Context B’s table has no entry for that id, so we return None. This is the gentlest reading of “an iter belongs to its originating Context” — no new error variant, no capability trap; the iter simply looks exhausted to the foreign Context. A future stricter mode could surface a dedicated RuntimeError::IterNotOwnedByContext, but today’s host APIs don’t yet expose a way to attach an iter to a Context other than via iter() itself, so the implicit- exhausted reading is sufficient and matches the “no implicit ambient state” design promise.

Trait Implementations§

Source§

impl Default for Context

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
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.