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: AtomicU64Monotonic 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: AtomicU64Monotonic 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: boolSet 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: boolTracks 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
impl Context
Sourcepub fn new() -> Self
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).
Sourcepub fn sandboxed() -> Self
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.
pub fn with_root(self, node: Node) -> Self
pub fn with_analyzed(self, tree: Arc<AnalyzedTree>) -> Self
Sourcepub fn with_workspace(self, workspace: Arc<WorkspaceTree>) -> Self
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.
Sourcepub fn with_capabilities(self, capabilities: Capabilities) -> Self
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.
Sourcepub fn capabilities(&self) -> &Capabilities
pub fn capabilities(&self) -> &Capabilities
Read-only view of the sandbox capability grants.
Sourcepub fn analyzed(&self) -> Option<&Arc<AnalyzedTree>>
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.
Sourcepub fn module_resolvers(&self) -> &[Arc<dyn ModuleResolver>]
pub fn module_resolvers(&self) -> &[Arc<dyn ModuleResolver>]
Read-only view of the module-resolution chain, in consultation order (front wins).
Sourcepub fn prepend_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>)
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).
Sourcepub fn append_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>)
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.
Sourcepub fn register_fn<S: Into<String>>(
&mut self,
name: S,
gate: NativeFnGate,
func: Arc<dyn RelonFunction>,
)
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.
Sourcepub fn register_pure_fn<S: Into<String>>(
&mut self,
name: S,
func: Arc<dyn RelonFunction>,
)
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.
Sourcepub fn register_method<S: Into<String>, M: Into<String>>(
&mut self,
schema: S,
method: M,
gate: NativeFnGate,
func: Arc<dyn RelonFunction>,
)
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.
Sourcepub fn register_pure_method<S: Into<String>, M: Into<String>>(
&mut self,
schema: S,
method: M,
func: Arc<dyn RelonFunction>,
)
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.
pub fn register_decorator<S: Into<String>>( &mut self, name: S, plugin: Arc<dyn DecoratorPlugin>, )
pub fn register_schema<S: Into<String>>(&mut self, name: S, schema: Value)
pub fn enter_loading_module(&self, id: String) -> LoadingModuleGuard<'_>
pub fn analyzer_target(&self, id: NodeId) -> Option<Node>
Sourcepub fn next_iter_id(&self) -> u64
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.
Sourcepub fn iter_cursor_fetch_and_inc(
&self,
iter_id: u64,
len: usize,
) -> Option<usize>
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§
Auto Trait Implementations§
impl !Freeze for Context
impl !RefUnwindSafe for Context
impl !UnwindSafe for Context
impl Send for Context
impl Sync for Context
impl Unpin for Context
impl UnsafeUnpin for Context
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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