Skip to main content

sim_kernel/
op.rs

1//! The operation contract: keyed, shape-checked calls dispatched on objects.
2//!
3//! The kernel defines the [`Op`] trait, operation specs and keys, and the
4//! resolve/invoke surface plus the well-known core operation keys; libraries
5//! implement the concrete operations.
6
7use crate::{
8    capability::CapabilityName,
9    effect::Effect,
10    env::Cx,
11    error::{Error, Result},
12    id::Symbol,
13    ref_id::Ref,
14    shape_check::check_shape_ref,
15    term::OpKey,
16    value::Value,
17};
18
19/// The declared contract of an operation: its key, shapes, effects, and
20/// required capabilities.
21///
22/// An `OpSpec` is metadata the kernel checks before dispatch -- the
23/// [`args_shape`](OpSpec::args_shape) is validated against the input and the
24/// [`requires`](OpSpec::requires) capabilities are demanded from the [`Cx`] --
25/// while the concrete behavior lives in the [`Op`] that carries it.
26#[derive(Clone, Debug)]
27pub struct OpSpec {
28    /// The interned operation key (`namespace:name@vN`) this spec describes.
29    pub key: OpKey,
30    /// The subject the operation dispatches on.
31    pub subject: Ref,
32    /// The shape the input is checked against before invocation.
33    pub args_shape: Ref,
34    /// The shape the operation promises for its result.
35    pub result_shape: Ref,
36    /// The effect symbols the operation may emit.
37    pub effects: Vec<Symbol>,
38    /// The capabilities that must be granted before the operation runs.
39    pub requires: Vec<CapabilityName>,
40}
41
42impl OpSpec {
43    /// Builds a spec with no declared effects or capability requirements.
44    pub fn new(key: OpKey, subject: Ref, args_shape: Ref, result_shape: Ref) -> Self {
45        Self {
46            key,
47            subject,
48            args_shape,
49            result_shape,
50            effects: Vec::new(),
51            requires: Vec::new(),
52        }
53    }
54
55    /// Returns the spec with its effect set replaced.
56    pub fn with_effects(mut self, effects: Vec<Symbol>) -> Self {
57        self.effects = effects;
58        self
59    }
60
61    /// Returns the spec with one more required capability appended.
62    pub fn requiring(mut self, capability: CapabilityName) -> Self {
63        self.requires.push(capability);
64        self
65    }
66
67    /// Returns the spec with its required-capability set replaced.
68    pub fn with_requirements(mut self, requires: Vec<CapabilityName>) -> Self {
69        self.requires = requires;
70        self
71    }
72}
73
74/// A keyed, shape-checked operation dispatched on an object.
75///
76/// The kernel defines this contract and the well-known core keys; libraries
77/// implement concrete operations against it. Dispatch goes through
78/// [`invoke_op`], which checks the [`OpSpec`] before calling
79/// [`invoke_authorized`](Op::invoke_authorized).
80pub trait Op: Send + Sync {
81    /// Returns the operation's declared contract.
82    fn spec(&self) -> &OpSpec;
83    /// Runs the operation after the kernel has checked shapes and capabilities.
84    fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step>;
85}
86
87/// The outcome of invoking an [`Op`]: a value, an event batch, or a suspension.
88#[derive(Clone, Debug)]
89pub enum Step {
90    /// A completed value result.
91    Value(Value),
92    /// A batch of emitted events.
93    Events(Value),
94    /// A suspended computation carrying the pending [`Effect`].
95    Suspended(Box<Effect>),
96}
97
98/// An operation resolved against a target, ready to be invoked.
99///
100/// Produced by [`resolve_op`], this hides whether the operation is a native
101/// [`Op`] registered on the object or a kernel adapter synthesized from one of
102/// the object's protocol surfaces.
103pub struct ResolvedOp<'a> {
104    inner: ResolvedOpKind<'a>,
105}
106
107impl<'a> ResolvedOp<'a> {
108    /// Returns the resolved operation's contract.
109    pub fn spec(&self) -> &OpSpec {
110        match &self.inner {
111            ResolvedOpKind::Native(op) => op.spec(),
112            ResolvedOpKind::Adapter(adapter) => &adapter.spec,
113        }
114    }
115
116    /// Runs the resolved operation; callers must check the spec first.
117    pub fn invoke_authorized(&self, cx: &mut Cx, input: Value) -> Result<Step> {
118        match &self.inner {
119            ResolvedOpKind::Native(op) => op.invoke_authorized(cx, input),
120            ResolvedOpKind::Adapter(adapter) => adapter.invoke(cx, input),
121        }
122    }
123}
124
125enum ResolvedOpKind<'a> {
126    Native(&'a dyn Op),
127    Adapter(Box<crate::op_adapters::AdapterOp<'a>>),
128}
129
130/// Resolves and runs an operation on `target`, enforcing its [`OpSpec`].
131///
132/// This is the kernel dispatch entry point: it resolves the key against the
133/// target (native [`Op`] or synthesized adapter), demands the required
134/// capabilities, checks the input against the declared args shape, and then
135/// invokes the operation. Only explicit `core:Any` and `core:AnyShape` refs opt
136/// out of shape checking.
137pub fn invoke_op(cx: &mut Cx, target: Value, key: &OpKey, input: Value) -> Result<Step> {
138    let resolved = resolve_op(cx, &target, key)?;
139    let requires = resolved.spec().requires.clone();
140    cx.require_all(&requires)?;
141    let args_shape = resolved.spec().args_shape.clone();
142    check_shape_if_available(cx, args_shape, input.clone())?;
143    resolved.invoke_authorized(cx, input)
144}
145
146/// Resolves an operation key against a target without invoking it.
147///
148/// Prefers a native [`Op`] registered on the object; otherwise falls back to a
149/// kernel adapter synthesized from one of the object's protocol surfaces, and
150/// errors when neither is available.
151pub fn resolve_op<'a>(cx: &mut Cx, target: &'a Value, key: &OpKey) -> Result<ResolvedOp<'a>> {
152    if let Some(op) = target.object().op(key) {
153        return Ok(ResolvedOp {
154            inner: ResolvedOpKind::Native(op),
155        });
156    }
157
158    let Some(adapter) = crate::op_adapters::resolve_adapter(cx, target, key)? else {
159        return Err(Error::Eval(format!(
160            "operation {} not available",
161            format_op_key(key)
162        )));
163    };
164
165    Ok(ResolvedOp {
166        inner: ResolvedOpKind::Adapter(Box::new(adapter)),
167    })
168}
169
170/// Checks `input` against a shape reference, passing silently only when the
171/// shape reference explicitly accepts anything.
172///
173/// Used by [`invoke_op`] to validate operation arguments. Missing symbols,
174/// unresolved refs, and registered non-shape values fail closed.
175pub fn check_shape_if_available(cx: &mut Cx, shape_ref: Ref, input: Value) -> Result<()> {
176    check_shape_ref(cx, &shape_ref, input)
177}
178
179/// The well-known key for the core call operation (`core:call@v1`).
180///
181/// # Examples
182///
183/// ```
184/// # use sim_kernel::op::core_call_op_key;
185/// let key = core_call_op_key();
186/// assert_eq!(key.namespace.to_string(), "core");
187/// assert_eq!(key.name.to_string(), "call");
188/// assert_eq!(key.version, 1);
189/// ```
190pub fn core_call_op_key() -> OpKey {
191    core_op_key("call")
192}
193
194/// The well-known key for checking a value against a shape.
195pub fn core_shape_check_value_op_key() -> OpKey {
196    core_op_key("shape-check-value")
197}
198
199/// The well-known key for checking a term against a shape.
200pub fn core_shape_check_term_op_key() -> OpKey {
201    core_op_key("shape-check-term")
202}
203
204/// The well-known key for describing a shape.
205pub fn core_shape_describe_op_key() -> OpKey {
206    core_op_key("shape-describe")
207}
208
209/// The well-known key for reading a class's symbol.
210pub fn core_class_symbol_op_key() -> OpKey {
211    core_op_key("class-symbol")
212}
213
214/// The well-known key for reading an object's encoding.
215pub fn core_object_encoding_op_key() -> OpKey {
216    core_op_key("object-encoding")
217}
218
219/// The well-known key for read-time construction.
220pub fn core_read_construct_op_key() -> OpKey {
221    core_op_key("read-construct")
222}
223
224/// The well-known key for reading a number's domain symbol.
225pub fn core_number_domain_symbol_op_key() -> OpKey {
226    core_op_key("number-domain-symbol")
227}
228
229/// The well-known key for reading a number value.
230pub fn core_number_value_op_key() -> OpKey {
231    core_op_key("number-value")
232}
233
234/// The well-known key for starting a distributed eval realization.
235pub fn core_realize_start_op_key() -> OpKey {
236    core_op_key("realize-start")
237}
238
239/// The well-known key for forcing a thunk.
240pub fn core_force_op_key() -> OpKey {
241    core_op_key("force")
242}
243
244/// The well-known key for advancing a sequence.
245pub fn core_seq_next_op_key() -> OpKey {
246    core_op_key("seq-next")
247}
248
249/// The well-known key for closing a sequence.
250pub fn core_seq_close_op_key() -> OpKey {
251    core_op_key("seq-close")
252}
253
254/// The well-known key for reading a list's items.
255pub fn core_list_items_op_key() -> OpKey {
256    core_op_key("list-items")
257}
258
259/// The well-known key for reading a table's entries.
260pub fn core_table_entries_op_key() -> OpKey {
261    core_op_key("table-entries")
262}
263
264/// The well-known key for testing whether a directory entry is a directory.
265pub fn core_dir_is_dir_op_key() -> OpKey {
266    core_op_key("dir-is-dir")
267}
268
269/// The well-known key for snapshotting an object as an [`Expr`](crate::Expr).
270pub fn core_expr_snapshot_op_key() -> OpKey {
271    core_op_key("expr-snapshot")
272}
273
274/// The shape reference that accepts any value (`core:Any`).
275pub fn core_any_ref() -> Ref {
276    core_ref("Any")
277}
278
279fn core_op_key(name: &str) -> OpKey {
280    OpKey::new(Symbol::new("core"), Symbol::new(name), 1)
281}
282
283pub(crate) fn core_ref(name: &str) -> Ref {
284    Ref::Symbol(core_symbol(name))
285}
286
287pub(crate) fn core_symbol(name: &str) -> Symbol {
288    Symbol::qualified("core", name)
289}
290
291fn format_op_key(key: &OpKey) -> String {
292    format!("{}:{}@v{}", key.namespace, key.name, key.version)
293}