Skip to main content

relon_eval_api/
lib.rs

1//! Public, backend-agnostic surface for Relon evaluation.
2//!
3//! This crate is the seam between hosts and evaluator backends; it only
4//! re-exports the types a caller actually sees:
5//!
6//! * Data shapes: [`Value`], [`Scope`], [`Thunk`], [`RuntimeError`].
7//! * Host configuration surface: [`Context`], [`Capabilities`],
8//!   [`NativeFnGate`], native-fn / decorator registration.
9//! * Policy boundary: the [`CapabilityGate`] trait — single source of
10//!   capability-policy truth consulted by every backend (see
11//!   `capability` module docs for the enforcement-timing diff
12//!   between dispatch-time tree-walker checks and vtable-build-time
13//!   cranelift checks).
14//! * Backend contract: the [`Evaluator`] trait — five `&self` methods
15//!   covering one full evaluation lifecycle.
16//!
17//! A backend (tree-walking `relon_evaluator::TreeWalkEvaluator`,
18//! cranelift AOT, LLVM AOT, wasm host wrapper, ...) implements this
19//! single trait; hosts then hold a `Box<dyn Evaluator>` for dynamic
20//! dispatch / backend swap.
21//!
22//! Trait object-safety is a hard requirement: every method is `&self` plus
23//! concrete in/out types — no generic methods.
24
25// `unsafe_code` is forbidden everywhere except the SSO `SmolStr` module,
26// which needs a single `str::from_utf8_unchecked` on the inline-payload
27// borrow to keep `as_str()` cost on par with `String::as_str()`. The
28// invariant is local — every constructor fills `data[..len]` from a
29// `&str` / `String` so the bytes are UTF-8 by construction — and the
30// `unsafe` block has an explicit `// SAFETY:` comment documenting it.
31#![deny(unsafe_code)]
32// rustc ≥ 1.93 false-positive: `unused_assignments` fires on fields of every
33// `#[derive(miette::Diagnostic)]` / `thiserror::Error` enum (the derive
34// expands to internal let-bindings that the lint mis-reads). Mirror the
35// evaluator crate's allow and drop it once the rustc fix lands.
36#![allow(unused_assignments)]
37
38pub mod buffer;
39pub mod capability;
40pub mod context;
41pub mod decorator;
42pub mod error;
43pub mod inplace_return;
44pub mod layout;
45pub mod module;
46pub mod native_fn;
47pub mod schema_canonical;
48pub mod schema_lower;
49pub mod scope;
50pub mod smol_str;
51pub mod value;
52pub mod verifier;
53
54pub use capability::CapabilityGate;
55pub use context::{
56    Capabilities, CapabilityBit, Context, GatedNativeFn, LoadingModuleGuard, NativeFnGate,
57    ResourceBudget, ResourceBudgetProfile,
58};
59pub use decorator::{DecoratorPlugin, PreEvalOutcome};
60pub use error::RuntimeError;
61pub use module::{ModuleResolver, ModuleSource};
62pub use native_fn::{EvaluatedArg, NativeArgs, NativeFn, NativeFnCaps, RelonFunction};
63pub use scope::{ListContext, Locals, RootRef, Scope, Thunk, Thunks};
64pub use smol_str::{SmolStr, SMOL_STR_INLINE_CAP};
65pub use value::{ClosureData, EnumSchemaData, SchemaData, SchemaField, Value, ValueDict};
66
67use std::collections::HashMap;
68use std::sync::Arc;
69
70/// Backend-agnostic evaluator contract.
71///
72/// Implementations turn an analyzed AST into a [`Value`]. The interface is
73/// deliberately object-safe: every method is `&self` with concrete-type
74/// arguments and return values — no generic methods — so hosts can hold a
75/// `Box<dyn Evaluator>` for backend swap or dynamic dispatch.
76///
77/// The five methods cover one full evaluation lifecycle:
78///
79/// * [`eval`](Self::eval) — evaluate a single node (fragment / debug entry).
80/// * [`eval_root`](Self::eval_root) — evaluate the document attached via
81///   `Context::with_root` as a library / static config.
82/// * [`run_main`](Self::run_main) — evaluate the document as an entry
83///   program: check host `args` against the `#main(...)` signature, bind
84///   them, then walk the body.
85/// * [`force_thunk`](Self::force_thunk) — drive a lazy dict entry to a
86///   value, caching the result for later accesses.
87/// * [`invoke_closure`](Self::invoke_closure) — call a constructed closure
88///   value with positional args; the shortest entry point for hosts that
89///   treat Relon closures as plain callbacks.
90pub trait Evaluator: Send + Sync {
91    /// Evaluate a single AST node under `scope`.
92    fn eval(&self, node: &relon_parser::Node, scope: &Arc<Scope>) -> Result<Value, RuntimeError>;
93
94    /// Evaluate the document attached via `Context::with_root` as a library
95    /// / static config (no `#main(...)` consultation, no host args).
96    fn eval_root(&self, scope: &Arc<Scope>) -> Result<Value, RuntimeError>;
97
98    /// Evaluate the document as an entry program: check `args` against the
99    /// file's `#main(...)` signature, bind them, then walk the body.
100    ///
101    /// Tuple parameters must be supplied as `Value::Tuple` (or
102    /// `Value::tuple(...)`). Targetless JSON decoding such as
103    /// `serde_json::from_value::<Value>` maps JSON arrays to `Value::List`,
104    /// which intentionally does not satisfy `Tuple<...>` parameters; JSON
105    /// `null` is rejected unless a host boundary decodes it against an
106    /// explicit `Option<T>` or `T?` target.
107    ///
108    /// Returns `NoMainSignature` if the file lacks `#main(...)`.
109    fn run_main(&self, args: HashMap<String, Value>) -> Result<Value, RuntimeError>;
110
111    /// Drive a lazy thunk to a value. The first call evaluates `thunk.node`
112    /// under `thunk.scope` and caches the result; later calls return the
113    /// cached value.
114    fn force_thunk(&self, thunk: &Arc<Thunk>) -> Result<Value, RuntimeError>;
115
116    /// Invoke a constructed closure value with positional `args`. The
117    /// shortest entry point when a host wants to call a Relon closure as a
118    /// plain callback.
119    fn invoke_closure(&self, closure: &ClosureData, args: &[Value]) -> Result<Value, RuntimeError>;
120}