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